llvm-project
473 строки · 14.7 Кб
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) {
24return "*" + Text.str() + "*";
25}
26
27static std::string genEmphasis(const Twine &Text) {
28return "**" + Text.str() + "**";
29}
30
31static std::string
32genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) {
33std::string Buffer;
34llvm::raw_string_ostream Stream(Buffer);
35for (const auto &R : Refs) {
36if (&R != Refs.begin())
37Stream << ", ";
38Stream << R.Name;
39}
40return Stream.str();
41}
42
43static void writeLine(const Twine &Text, raw_ostream &OS) {
44OS << 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) {
50OS << std::string(Num, '#') + " " + Text << "\n\n";
51}
52
53static void writeFileDefinition(const ClangDocContext &CDCtx, const Location &L,
54raw_ostream &OS) {
55
56if (!CDCtx.RepositoryUrl) {
57OS << "*Defined at " << L.Filename << "#" << std::to_string(L.LineNumber)
58<< "*";
59} else {
60OS << "*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}
66OS << "\n\n";
67}
68
69static void writeDescription(const CommentInfo &I, raw_ostream &OS) {
70if (I.Kind == "FullComment") {
71for (const auto &Child : I.Children)
72writeDescription(*Child, OS);
73} else if (I.Kind == "ParagraphComment") {
74for (const auto &Child : I.Children)
75writeDescription(*Child, OS);
76writeNewLine(OS);
77} else if (I.Kind == "BlockCommandComment") {
78OS << genEmphasis(I.Name);
79for (const auto &Child : I.Children)
80writeDescription(*Child, OS);
81} else if (I.Kind == "InlineCommandComment") {
82OS << genEmphasis(I.Name) << " " << I.Text;
83} else if (I.Kind == "ParamCommandComment") {
84std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
85OS << genEmphasis(I.ParamName) << I.Text << Direction;
86for (const auto &Child : I.Children)
87writeDescription(*Child, OS);
88} else if (I.Kind == "TParamCommandComment") {
89std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
90OS << genEmphasis(I.ParamName) << I.Text << Direction;
91for (const auto &Child : I.Children)
92writeDescription(*Child, OS);
93} else if (I.Kind == "VerbatimBlockComment") {
94for (const auto &Child : I.Children)
95writeDescription(*Child, OS);
96} else if (I.Kind == "VerbatimBlockLineComment") {
97OS << I.Text;
98writeNewLine(OS);
99} else if (I.Kind == "VerbatimLineComment") {
100OS << I.Text;
101writeNewLine(OS);
102} else if (I.Kind == "HTMLStartTagComment") {
103if (I.AttrKeys.size() != I.AttrValues.size())
104return;
105std::string Buffer;
106llvm::raw_string_ostream Attrs(Buffer);
107for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx)
108Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\"";
109
110std::string CloseTag = I.SelfClosing ? "/>" : ">";
111writeLine("<" + I.Name + Attrs.str() + CloseTag, OS);
112} else if (I.Kind == "HTMLEndTagComment") {
113writeLine("</" + I.Name + ">", OS);
114} else if (I.Kind == "TextComment") {
115OS << I.Text;
116} else {
117OS << "Unknown comment kind: " << I.Kind << ".\n\n";
118}
119}
120
121static void writeNameLink(const StringRef &CurrentPath, const Reference &R,
122llvm::raw_ostream &OS) {
123llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath);
124// Paths in Markdown use POSIX separators.
125llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
126llvm::sys::path::append(Path, llvm::sys::path::Style::posix,
127R.getFileBaseName() + ".md");
128OS << "[" << R.Name << "](" << Path << ")";
129}
130
131static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I,
132llvm::raw_ostream &OS) {
133if (I.Scoped)
134writeLine("| enum class " + I.Name + " |", OS);
135else
136writeLine("| enum " + I.Name + " |", OS);
137writeLine("--", OS);
138
139std::string Buffer;
140llvm::raw_string_ostream Members(Buffer);
141if (!I.Members.empty())
142for (const auto &N : I.Members)
143Members << "| " << N.Name << " |\n";
144writeLine(Members.str(), OS);
145if (I.DefLoc)
146writeFileDefinition(CDCtx, *I.DefLoc, OS);
147
148for (const auto &C : I.Description)
149writeDescription(C, OS);
150}
151
152static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
153llvm::raw_ostream &OS) {
154std::string Buffer;
155llvm::raw_string_ostream Stream(Buffer);
156bool First = true;
157for (const auto &N : I.Params) {
158if (!First)
159Stream << ", ";
160Stream << N.Type.Name + " " + N.Name;
161First = false;
162}
163writeHeader(I.Name, 3, OS);
164std::string Access = getAccessSpelling(I.Access).str();
165if (Access != "")
166writeLine(genItalic(Access + " " + I.ReturnType.Type.Name + " " + I.Name +
167"(" + Stream.str() + ")"),
168OS);
169else
170writeLine(genItalic(I.ReturnType.Type.Name + " " + I.Name + "(" +
171Stream.str() + ")"),
172OS);
173if (I.DefLoc)
174writeFileDefinition(CDCtx, *I.DefLoc, OS);
175
176for (const auto &C : I.Description)
177writeDescription(C, OS);
178}
179
180static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I,
181llvm::raw_ostream &OS) {
182if (I.Name == "")
183writeHeader("Global Namespace", 1, OS);
184else
185writeHeader("namespace " + I.Name, 1, OS);
186writeNewLine(OS);
187
188if (!I.Description.empty()) {
189for (const auto &C : I.Description)
190writeDescription(C, OS);
191writeNewLine(OS);
192}
193
194llvm::SmallString<64> BasePath = I.getRelativeFilePath("");
195
196if (!I.Children.Namespaces.empty()) {
197writeHeader("Namespaces", 2, OS);
198for (const auto &R : I.Children.Namespaces) {
199OS << "* ";
200writeNameLink(BasePath, R, OS);
201OS << "\n";
202}
203writeNewLine(OS);
204}
205
206if (!I.Children.Records.empty()) {
207writeHeader("Records", 2, OS);
208for (const auto &R : I.Children.Records) {
209OS << "* ";
210writeNameLink(BasePath, R, OS);
211OS << "\n";
212}
213writeNewLine(OS);
214}
215
216if (!I.Children.Functions.empty()) {
217writeHeader("Functions", 2, OS);
218for (const auto &F : I.Children.Functions)
219genMarkdown(CDCtx, F, OS);
220writeNewLine(OS);
221}
222if (!I.Children.Enums.empty()) {
223writeHeader("Enums", 2, OS);
224for (const auto &E : I.Children.Enums)
225genMarkdown(CDCtx, E, OS);
226writeNewLine(OS);
227}
228}
229
230static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
231llvm::raw_ostream &OS) {
232writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
233if (I.DefLoc)
234writeFileDefinition(CDCtx, *I.DefLoc, OS);
235
236if (!I.Description.empty()) {
237for (const auto &C : I.Description)
238writeDescription(C, OS);
239writeNewLine(OS);
240}
241
242std::string Parents = genReferenceList(I.Parents);
243std::string VParents = genReferenceList(I.VirtualParents);
244if (!Parents.empty() || !VParents.empty()) {
245if (Parents.empty())
246writeLine("Inherits from " + VParents, OS);
247else if (VParents.empty())
248writeLine("Inherits from " + Parents, OS);
249else
250writeLine("Inherits from " + Parents + ", " + VParents, OS);
251writeNewLine(OS);
252}
253
254if (!I.Members.empty()) {
255writeHeader("Members", 2, OS);
256for (const auto &Member : I.Members) {
257std::string Access = getAccessSpelling(Member.Access).str();
258if (Access != "")
259writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS);
260else
261writeLine(Member.Type.Name + " " + Member.Name, OS);
262}
263writeNewLine(OS);
264}
265
266if (!I.Children.Records.empty()) {
267writeHeader("Records", 2, OS);
268for (const auto &R : I.Children.Records)
269writeLine(R.Name, OS);
270writeNewLine(OS);
271}
272if (!I.Children.Functions.empty()) {
273writeHeader("Functions", 2, OS);
274for (const auto &F : I.Children.Functions)
275genMarkdown(CDCtx, F, OS);
276writeNewLine(OS);
277}
278if (!I.Children.Enums.empty()) {
279writeHeader("Enums", 2, OS);
280for (const auto &E : I.Children.Enums)
281genMarkdown(CDCtx, E, OS);
282writeNewLine(OS);
283}
284}
285
286static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I,
287llvm::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 ##
293OS << "##" << std::string(Level, '#') << " ";
294writeNameLink("", I, OS);
295OS << "\n";
296}
297
298static llvm::Error serializeIndex(ClangDocContext &CDCtx) {
299std::error_code FileErr;
300llvm::SmallString<128> FilePath;
301llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
302llvm::sys::path::append(FilePath, "all_files.md");
303llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
304if (FileErr)
305return llvm::createStringError(llvm::inconvertibleErrorCode(),
306"error creating index file: " +
307FileErr.message());
308
309CDCtx.Idx.sort();
310OS << "# All Files";
311if (!CDCtx.ProjectName.empty())
312OS << " for " << CDCtx.ProjectName;
313OS << "\n\n";
314
315for (auto C : CDCtx.Idx.Children)
316serializeReference(OS, C, 0);
317
318return llvm::Error::success();
319}
320
321static llvm::Error genIndex(ClangDocContext &CDCtx) {
322std::error_code FileErr;
323llvm::SmallString<128> FilePath;
324llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
325llvm::sys::path::append(FilePath, "index.md");
326llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
327if (FileErr)
328return llvm::createStringError(llvm::inconvertibleErrorCode(),
329"error creating index file: " +
330FileErr.message());
331CDCtx.Idx.sort();
332OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n";
333for (auto C : CDCtx.Idx.Children) {
334if (!C.Children.empty()) {
335const char *Type;
336switch (C.RefType) {
337case InfoType::IT_namespace:
338Type = "Namespace";
339break;
340case InfoType::IT_record:
341Type = "Type";
342break;
343case InfoType::IT_enum:
344Type = "Enum";
345break;
346case InfoType::IT_function:
347Type = "Function";
348break;
349case InfoType::IT_typedef:
350Type = "Typedef";
351break;
352case InfoType::IT_default:
353Type = "Other";
354}
355OS << "* " << Type << ": [" << C.Name << "](";
356if (!C.Path.empty())
357OS << C.Path << "/";
358OS << C.Name << ")\n";
359}
360}
361return llvm::Error::success();
362}
363
364/// Generator for Markdown documentation.
365class MDGenerator : public Generator {
366public:
367static const char *Format;
368
369llvm::Error generateDocs(StringRef RootDir,
370llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
371const ClangDocContext &CDCtx) override;
372llvm::Error createResources(ClangDocContext &CDCtx) override;
373llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
374const ClangDocContext &CDCtx) override;
375};
376
377const char *MDGenerator::Format = "md";
378
379llvm::Error
380MDGenerator::generateDocs(StringRef RootDir,
381llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
382const ClangDocContext &CDCtx) {
383// Track which directories we already tried to create.
384llvm::StringSet<> CreatedDirs;
385
386// Collect all output by file name and create the necessary directories.
387llvm::StringMap<std::vector<doc::Info *>> FileToInfos;
388for (const auto &Group : Infos) {
389doc::Info *Info = Group.getValue().get();
390
391llvm::SmallString<128> Path;
392llvm::sys::path::native(RootDir, Path);
393llvm::sys::path::append(Path, Info->getRelativeFilePath(""));
394if (!CreatedDirs.contains(Path)) {
395if (std::error_code Err = llvm::sys::fs::create_directories(Path);
396Err != std::error_code()) {
397return llvm::createStringError(Err, "Failed to create directory '%s'.",
398Path.c_str());
399}
400CreatedDirs.insert(Path);
401}
402
403llvm::sys::path::append(Path, Info->getFileBaseName() + ".md");
404FileToInfos[Path].push_back(Info);
405}
406
407for (const auto &Group : FileToInfos) {
408std::error_code FileErr;
409llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,
410llvm::sys::fs::OF_None);
411if (FileErr) {
412return llvm::createStringError(FileErr, "Error opening file '%s'",
413Group.getKey().str().c_str());
414}
415
416for (const auto &Info : Group.getValue()) {
417if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {
418return Err;
419}
420}
421}
422
423return llvm::Error::success();
424}
425
426llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
427const ClangDocContext &CDCtx) {
428switch (I->IT) {
429case InfoType::IT_namespace:
430genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS);
431break;
432case InfoType::IT_record:
433genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS);
434break;
435case InfoType::IT_enum:
436genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS);
437break;
438case InfoType::IT_function:
439genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS);
440break;
441case InfoType::IT_typedef:
442genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS);
443break;
444case InfoType::IT_default:
445return createStringError(llvm::inconvertibleErrorCode(),
446"unexpected InfoType");
447}
448return llvm::Error::success();
449}
450
451llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) {
452// Write an all_files.md
453auto Err = serializeIndex(CDCtx);
454if (Err)
455return Err;
456
457// Generate the index page.
458Err = genIndex(CDCtx);
459if (Err)
460return Err;
461
462return 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