1///===----------------------------------------------------------------------===//
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/// \file
10/// This file contains the implementation of the MustacheHTMLGenerator class,
11/// which is Clang-Doc generator for HTML using Mustache templates.
12///
13//===----------------------------------------------------------------------===//
14
15#include "Generators.h"
16#include "Representation.h"
17#include "support/File.h"
18#include "llvm/Support/Error.h"
19#include "llvm/Support/MemoryBuffer.h"
20#include "llvm/Support/Mustache.h"
21#include "llvm/Support/Path.h"
22#include "llvm/Support/TimeProfiler.h"
23
24using namespace llvm;
25using namespace llvm::json;
26using namespace llvm::mustache;
27
28namespace clang {
29namespace doc {
30
31static Error createFileOpenError(StringRef FileName, std::error_code EC) {
32 return createFileError(F: "cannot open file " + FileName, EC);
33}
34
35class MustacheHTMLGenerator : public Generator {
36public:
37 static const char *Format;
38 Error generateDocs(StringRef RootDir,
39 StringMap<std::unique_ptr<doc::Info>> Infos,
40 const ClangDocContext &CDCtx) override;
41 Error createResources(ClangDocContext &CDCtx) override;
42 Error generateDocForInfo(Info *I, raw_ostream &OS,
43 const ClangDocContext &CDCtx) override;
44};
45
46class MustacheTemplateFile : public Template {
47public:
48 static Expected<std::unique_ptr<MustacheTemplateFile>>
49 createMustacheFile(StringRef FileName) {
50 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrError =
51 MemoryBuffer::getFile(Filename: FileName);
52 if (auto EC = BufferOrError.getError())
53 return createFileOpenError(FileName, EC);
54
55 std::unique_ptr<MemoryBuffer> Buffer = std::move(BufferOrError.get());
56 StringRef FileContent = Buffer->getBuffer();
57 return std::make_unique<MustacheTemplateFile>(args&: FileContent);
58 }
59
60 Error registerPartialFile(StringRef Name, StringRef FileName) {
61 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrError =
62 MemoryBuffer::getFile(Filename: FileName);
63 if (auto EC = BufferOrError.getError())
64 return createFileOpenError(FileName, EC);
65
66 std::unique_ptr<MemoryBuffer> Buffer = std::move(BufferOrError.get());
67 StringRef FileContent = Buffer->getBuffer();
68 registerPartial(Name: Name.str(), Partial: FileContent.str());
69 return Error::success();
70 }
71
72 MustacheTemplateFile(StringRef TemplateStr) : Template(TemplateStr) {}
73};
74
75static std::unique_ptr<MustacheTemplateFile> NamespaceTemplate = nullptr;
76
77static std::unique_ptr<MustacheTemplateFile> RecordTemplate = nullptr;
78
79static Error
80setupTemplate(std::unique_ptr<MustacheTemplateFile> &Template,
81 StringRef TemplatePath,
82 std::vector<std::pair<StringRef, StringRef>> Partials) {
83 auto T = MustacheTemplateFile::createMustacheFile(FileName: TemplatePath);
84 if (Error Err = T.takeError())
85 return Err;
86 Template = std::move(T.get());
87 for (const auto &[Name, FileName] : Partials)
88 if (auto Err = Template->registerPartialFile(Name, FileName))
89 return Err;
90 return Error::success();
91}
92
93static Error setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) {
94 // Template files need to use the native path when they're opened,
95 // but have to be used in POSIX style when used in HTML.
96 auto ConvertToNative = [](std::string &&Path) -> std::string {
97 SmallString<128> PathBuf(Path);
98 llvm::sys::path::native(path&: PathBuf);
99 return PathBuf.str().str();
100 };
101
102 std::string NamespaceFilePath =
103 ConvertToNative(CDCtx.MustacheTemplates.lookup(Key: "namespace-template"));
104 std::string ClassFilePath =
105 ConvertToNative(CDCtx.MustacheTemplates.lookup(Key: "class-template"));
106 std::string CommentFilePath =
107 ConvertToNative(CDCtx.MustacheTemplates.lookup(Key: "comment-template"));
108 std::string FunctionFilePath =
109 ConvertToNative(CDCtx.MustacheTemplates.lookup(Key: "function-template"));
110 std::string EnumFilePath =
111 ConvertToNative(CDCtx.MustacheTemplates.lookup(Key: "enum-template"));
112 std::vector<std::pair<StringRef, StringRef>> Partials = {
113 {"Comments", CommentFilePath},
114 {"FunctionPartial", FunctionFilePath},
115 {"EnumPartial", EnumFilePath}};
116
117 if (Error Err = setupTemplate(Template&: NamespaceTemplate, TemplatePath: NamespaceFilePath, Partials))
118 return Err;
119
120 if (Error Err = setupTemplate(Template&: RecordTemplate, TemplatePath: ClassFilePath, Partials))
121 return Err;
122
123 return Error::success();
124}
125
126Error MustacheHTMLGenerator::generateDocs(
127 StringRef RootDir, StringMap<std::unique_ptr<doc::Info>> Infos,
128 const clang::doc::ClangDocContext &CDCtx) {
129 {
130 llvm::TimeTraceScope TS("Setup Templates");
131 if (auto Err = setupTemplateFiles(CDCtx))
132 return Err;
133 }
134
135 // Track which directories we already tried to create.
136 StringSet<> CreatedDirs;
137 // Collect all output by file name and create the necessary directories.
138 StringMap<std::vector<doc::Info *>> FileToInfos;
139 for (const auto &Group : Infos) {
140 llvm::TimeTraceScope TS("setup directories");
141 doc::Info *Info = Group.getValue().get();
142
143 SmallString<128> Path;
144 sys::path::native(path: RootDir, result&: Path);
145 sys::path::append(path&: Path, a: Info->getRelativeFilePath(CurrentPath: ""));
146 if (!CreatedDirs.contains(key: Path)) {
147 if (std::error_code EC = sys::fs::create_directories(path: Path))
148 return createStringError(EC, Fmt: "failed to create directory '%s'.",
149 Vals: Path.c_str());
150 CreatedDirs.insert(key: Path);
151 }
152
153 sys::path::append(path&: Path, a: Info->getFileBaseName() + ".html");
154 FileToInfos[Path].push_back(x: Info);
155 }
156
157 {
158 llvm::TimeTraceScope TS("Generate Docs");
159 for (const auto &Group : FileToInfos) {
160 llvm::TimeTraceScope TS("Info to Doc");
161 std::error_code FileErr;
162 raw_fd_ostream InfoOS(Group.getKey(), FileErr, sys::fs::OF_None);
163 if (FileErr)
164 return createFileOpenError(FileName: Group.getKey(), EC: FileErr);
165
166 for (const auto &Info : Group.getValue())
167 if (Error Err = generateDocForInfo(I: Info, OS&: InfoOS, CDCtx))
168 return Err;
169 }
170 }
171 return Error::success();
172}
173
174static json::Value
175extractValue(const Location &L,
176 std::optional<StringRef> RepositoryUrl = std::nullopt) {
177 Object Obj = Object();
178 // TODO: Consider using both Start/End line numbers to improve location report
179 Obj.insert(E: {.K: "LineNumber", .V: L.StartLineNumber});
180 Obj.insert(E: {.K: "Filename", .V: L.Filename});
181
182 if (!L.IsFileInRootDir || !RepositoryUrl)
183 return Obj;
184 SmallString<128> FileURL(*RepositoryUrl);
185 sys::path::append(path&: FileURL, style: sys::path::Style::posix, a: L.Filename);
186 FileURL += "#" + std::to_string(val: L.StartLineNumber);
187 Obj.insert(E: {.K: "FileURL", .V: FileURL});
188
189 return Obj;
190}
191
192static json::Value extractValue(const Reference &I,
193 StringRef CurrentDirectory) {
194 SmallString<64> Path = I.getRelativeFilePath(CurrentPath: CurrentDirectory);
195 sys::path::append(path&: Path, a: I.getFileBaseName() + ".html");
196 sys::path::native(path&: Path, style: sys::path::Style::posix);
197 Object Obj = Object();
198 Obj.insert(E: {.K: "Link", .V: Path});
199 Obj.insert(E: {.K: "Name", .V: I.Name});
200 Obj.insert(E: {.K: "QualName", .V: I.QualName});
201 Obj.insert(E: {.K: "ID", .V: toHex(Input: toStringRef(Input: I.USR))});
202 return Obj;
203}
204
205static json::Value extractValue(const TypedefInfo &I) {
206 // Not Supported
207 return nullptr;
208}
209
210static json::Value extractValue(const CommentInfo &I) {
211 Object Obj = Object();
212
213 json::Value ChildVal = Object();
214 Object &Child = *ChildVal.getAsObject();
215
216 json::Value ChildArr = Array();
217 auto &CARef = *ChildArr.getAsArray();
218 CARef.reserve(S: I.Children.size());
219 for (const auto &C : I.Children)
220 CARef.emplace_back(A: extractValue(I: *C));
221
222 switch (I.Kind) {
223 case CommentKind::CK_TextComment: {
224 Obj.insert(E: {.K: commentKindToString(Kind: I.Kind), .V: I.Text});
225 return Obj;
226 }
227
228 case CommentKind::CK_BlockCommandComment: {
229 Child.insert(E: {.K: "Command", .V: I.Name});
230 Child.insert(E: {.K: "Children", .V: ChildArr});
231 Obj.insert(E: {.K: commentKindToString(Kind: I.Kind), .V: ChildVal});
232 return Obj;
233 }
234
235 case CommentKind::CK_InlineCommandComment: {
236 json::Value ArgsArr = Array();
237 auto &ARef = *ArgsArr.getAsArray();
238 ARef.reserve(S: I.Args.size());
239 for (const auto &Arg : I.Args)
240 ARef.emplace_back(A: Arg);
241 Child.insert(E: {.K: "Command", .V: I.Name});
242 Child.insert(E: {.K: "Args", .V: ArgsArr});
243 Child.insert(E: {.K: "Children", .V: ChildArr});
244 Obj.insert(E: {.K: commentKindToString(Kind: I.Kind), .V: ChildVal});
245 return Obj;
246 }
247
248 case CommentKind::CK_ParamCommandComment:
249 case CommentKind::CK_TParamCommandComment: {
250 Child.insert(E: {.K: "ParamName", .V: I.ParamName});
251 Child.insert(E: {.K: "Direction", .V: I.Direction});
252 Child.insert(E: {.K: "Explicit", .V: I.Explicit});
253 Child.insert(E: {.K: "Children", .V: ChildArr});
254 Obj.insert(E: {.K: commentKindToString(Kind: I.Kind), .V: ChildVal});
255 return Obj;
256 }
257
258 case CommentKind::CK_VerbatimBlockComment: {
259 Child.insert(E: {.K: "Text", .V: I.Text});
260 if (!I.CloseName.empty())
261 Child.insert(E: {.K: "CloseName", .V: I.CloseName});
262 Child.insert(E: {.K: "Children", .V: ChildArr});
263 Obj.insert(E: {.K: commentKindToString(Kind: I.Kind), .V: ChildVal});
264 return Obj;
265 }
266
267 case CommentKind::CK_VerbatimBlockLineComment:
268 case CommentKind::CK_VerbatimLineComment: {
269 Child.insert(E: {.K: "Text", .V: I.Text});
270 Child.insert(E: {.K: "Children", .V: ChildArr});
271 Obj.insert(E: {.K: commentKindToString(Kind: I.Kind), .V: ChildVal});
272 return Obj;
273 }
274
275 case CommentKind::CK_HTMLStartTagComment: {
276 json::Value AttrKeysArray = json::Array();
277 json::Value AttrValuesArray = json::Array();
278 auto &KeyArr = *AttrKeysArray.getAsArray();
279 auto &ValArr = *AttrValuesArray.getAsArray();
280 KeyArr.reserve(S: I.AttrKeys.size());
281 ValArr.reserve(S: I.AttrValues.size());
282 for (const auto &K : I.AttrKeys)
283 KeyArr.emplace_back(A: K);
284 for (const auto &V : I.AttrValues)
285 ValArr.emplace_back(A: V);
286 Child.insert(E: {.K: "Name", .V: I.Name});
287 Child.insert(E: {.K: "SelfClosing", .V: I.SelfClosing});
288 Child.insert(E: {.K: "AttrKeys", .V: AttrKeysArray});
289 Child.insert(E: {.K: "AttrValues", .V: AttrValuesArray});
290 Child.insert(E: {.K: "Children", .V: ChildArr});
291 Obj.insert(E: {.K: commentKindToString(Kind: I.Kind), .V: ChildVal});
292 return Obj;
293 }
294
295 case CommentKind::CK_HTMLEndTagComment: {
296 Child.insert(E: {.K: "Name", .V: I.Name});
297 Child.insert(E: {.K: "Children", .V: ChildArr});
298 Obj.insert(E: {.K: commentKindToString(Kind: I.Kind), .V: ChildVal});
299 return Obj;
300 }
301
302 case CommentKind::CK_FullComment:
303 case CommentKind::CK_ParagraphComment: {
304 Child.insert(E: {.K: "Children", .V: ChildArr});
305 Obj.insert(E: {.K: commentKindToString(Kind: I.Kind), .V: ChildVal});
306 return Obj;
307 }
308
309 case CommentKind::CK_Unknown: {
310 Obj.insert(E: {.K: commentKindToString(Kind: I.Kind), .V: I.Text});
311 return Obj;
312 }
313 }
314 llvm_unreachable("Unknown comment kind encountered.");
315}
316
317static void maybeInsertLocation(std::optional<Location> Loc,
318 const ClangDocContext &CDCtx, Object &Obj) {
319 if (!Loc)
320 return;
321 Location L = *Loc;
322 Obj.insert(E: {.K: "Location", .V: extractValue(L, RepositoryUrl: CDCtx.RepositoryUrl)});
323}
324
325static void extractDescriptionFromInfo(ArrayRef<CommentInfo> Descriptions,
326 json::Object &EnumValObj) {
327 if (Descriptions.empty())
328 return;
329 json::Value DescArr = Array();
330 json::Array &DescARef = *DescArr.getAsArray();
331 DescARef.reserve(S: Descriptions.size());
332 for (const CommentInfo &Child : Descriptions)
333 DescARef.emplace_back(A: extractValue(I: Child));
334 EnumValObj.insert(E: {.K: "EnumValueComments", .V: DescArr});
335}
336
337static json::Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir,
338 const ClangDocContext &CDCtx) {
339 Object Obj = Object();
340 Obj.insert(E: {.K: "Name", .V: I.Name});
341 Obj.insert(E: {.K: "ID", .V: toHex(Input: toStringRef(Input: I.USR))});
342 Obj.insert(E: {.K: "Access", .V: getAccessSpelling(AS: I.Access).str()});
343 Obj.insert(E: {.K: "ReturnType", .V: extractValue(I: I.ReturnType.Type, CurrentDirectory: ParentInfoDir)});
344
345 json::Value ParamArr = Array();
346 json::Array &ParamARef = *ParamArr.getAsArray();
347 ParamARef.reserve(S: I.Params.size());
348 for (const auto Val : enumerate(First: I.Params)) {
349 json::Value V = Object();
350 auto &VRef = *V.getAsObject();
351 VRef.insert(E: {.K: "Name", .V: Val.value().Name});
352 VRef.insert(E: {.K: "Type", .V: Val.value().Type.Name});
353 VRef.insert(E: {.K: "End", .V: Val.index() + 1 == I.Params.size()});
354 ParamARef.emplace_back(A&: V);
355 }
356 Obj.insert(E: {.K: "Params", .V: ParamArr});
357
358 maybeInsertLocation(Loc: I.DefLoc, CDCtx, Obj);
359 return Obj;
360}
361
362static json::Value extractValue(const EnumInfo &I,
363 const ClangDocContext &CDCtx) {
364 Object Obj = Object();
365 std::string EnumType = I.Scoped ? "enum class " : "enum ";
366 EnumType += I.Name;
367 bool HasComment = llvm::any_of(
368 Range: I.Members, P: [](const EnumValueInfo &M) { return !M.Description.empty(); });
369 Obj.insert(E: {.K: "EnumName", .V: EnumType});
370 Obj.insert(E: {.K: "HasComment", .V: HasComment});
371 Obj.insert(E: {.K: "ID", .V: toHex(Input: toStringRef(Input: I.USR))});
372 json::Value EnumArr = Array();
373 json::Array &EnumARef = *EnumArr.getAsArray();
374 EnumARef.reserve(S: I.Members.size());
375 for (const EnumValueInfo &M : I.Members) {
376 json::Value EnumValue = Object();
377 auto &EnumValObj = *EnumValue.getAsObject();
378 EnumValObj.insert(E: {.K: "Name", .V: M.Name});
379 if (!M.ValueExpr.empty())
380 EnumValObj.insert(E: {.K: "ValueExpr", .V: M.ValueExpr});
381 else
382 EnumValObj.insert(E: {.K: "Value", .V: M.Value});
383
384 extractDescriptionFromInfo(Descriptions: M.Description, EnumValObj);
385 EnumARef.emplace_back(A&: EnumValue);
386 }
387 Obj.insert(E: {.K: "EnumValues", .V: EnumArr});
388
389 extractDescriptionFromInfo(Descriptions: I.Description, EnumValObj&: Obj);
390 maybeInsertLocation(Loc: I.DefLoc, CDCtx, Obj);
391
392 return Obj;
393}
394
395static void extractScopeChildren(const ScopeChildren &S, Object &Obj,
396 StringRef ParentInfoDir,
397 const ClangDocContext &CDCtx) {
398 json::Value NamespaceArr = Array();
399 json::Array &NamespaceARef = *NamespaceArr.getAsArray();
400 NamespaceARef.reserve(S: S.Namespaces.size());
401 for (const Reference &Child : S.Namespaces)
402 NamespaceARef.emplace_back(A: extractValue(I: Child, CurrentDirectory: ParentInfoDir));
403
404 if (!NamespaceARef.empty())
405 Obj.insert(E: {.K: "Namespace", .V: Object{{.K: "Links", .V: NamespaceArr}}});
406
407 json::Value RecordArr = Array();
408 json::Array &RecordARef = *RecordArr.getAsArray();
409 RecordARef.reserve(S: S.Records.size());
410 for (const Reference &Child : S.Records)
411 RecordARef.emplace_back(A: extractValue(I: Child, CurrentDirectory: ParentInfoDir));
412
413 if (!RecordARef.empty())
414 Obj.insert(E: {.K: "Record", .V: Object{{.K: "Links", .V: RecordArr}}});
415
416 json::Value FunctionArr = Array();
417 json::Array &FunctionARef = *FunctionArr.getAsArray();
418 FunctionARef.reserve(S: S.Functions.size());
419
420 json::Value PublicFunctionArr = Array();
421 json::Array &PublicFunctionARef = *PublicFunctionArr.getAsArray();
422 PublicFunctionARef.reserve(S: S.Functions.size());
423
424 json::Value ProtectedFunctionArr = Array();
425 json::Array &ProtectedFunctionARef = *ProtectedFunctionArr.getAsArray();
426 ProtectedFunctionARef.reserve(S: S.Functions.size());
427
428 for (const FunctionInfo &Child : S.Functions) {
429 json::Value F = extractValue(I: Child, ParentInfoDir, CDCtx);
430 AccessSpecifier Access = Child.Access;
431 if (Access == AccessSpecifier::AS_public)
432 PublicFunctionARef.emplace_back(A&: F);
433 else if (Access == AccessSpecifier::AS_protected)
434 ProtectedFunctionARef.emplace_back(A&: F);
435 else
436 FunctionARef.emplace_back(A&: F);
437 }
438
439 if (!FunctionARef.empty())
440 Obj.insert(E: {.K: "Function", .V: Object{{.K: "Obj", .V: FunctionArr}}});
441
442 if (!PublicFunctionARef.empty())
443 Obj.insert(E: {.K: "PublicFunction", .V: Object{{.K: "Obj", .V: PublicFunctionArr}}});
444
445 if (!ProtectedFunctionARef.empty())
446 Obj.insert(E: {.K: "ProtectedFunction", .V: Object{{.K: "Obj", .V: ProtectedFunctionArr}}});
447
448 json::Value EnumArr = Array();
449 auto &EnumARef = *EnumArr.getAsArray();
450 EnumARef.reserve(S: S.Enums.size());
451 for (const EnumInfo &Child : S.Enums)
452 EnumARef.emplace_back(A: extractValue(I: Child, CDCtx));
453
454 if (!EnumARef.empty())
455 Obj.insert(E: {.K: "Enums", .V: Object{{.K: "Obj", .V: EnumArr}}});
456
457 json::Value TypedefArr = Array();
458 auto &TypedefARef = *TypedefArr.getAsArray();
459 TypedefARef.reserve(S: S.Typedefs.size());
460 for (const TypedefInfo &Child : S.Typedefs)
461 TypedefARef.emplace_back(A: extractValue(I: Child));
462
463 if (!TypedefARef.empty())
464 Obj.insert(E: {.K: "Typedefs", .V: Object{{.K: "Obj", .V: TypedefArr}}});
465}
466
467static json::Value extractValue(const NamespaceInfo &I,
468 const ClangDocContext &CDCtx) {
469 Object NamespaceValue = Object();
470 std::string InfoTitle = I.Name.empty() ? "Global Namespace"
471 : (Twine("namespace ") + I.Name).str();
472
473 SmallString<64> BasePath = I.getRelativeFilePath(CurrentPath: "");
474 NamespaceValue.insert(E: {.K: "NamespaceTitle", .V: InfoTitle});
475 NamespaceValue.insert(E: {.K: "NamespacePath", .V: BasePath});
476
477 extractDescriptionFromInfo(Descriptions: I.Description, EnumValObj&: NamespaceValue);
478 extractScopeChildren(S: I.Children, Obj&: NamespaceValue, ParentInfoDir: BasePath, CDCtx);
479 return NamespaceValue;
480}
481
482static json::Value extractValue(const RecordInfo &I,
483 const ClangDocContext &CDCtx) {
484 Object RecordValue = Object();
485 extractDescriptionFromInfo(Descriptions: I.Description, EnumValObj&: RecordValue);
486 RecordValue.insert(E: {.K: "Name", .V: I.Name});
487 RecordValue.insert(E: {.K: "FullName", .V: I.FullName});
488 RecordValue.insert(E: {.K: "RecordType", .V: getTagType(AS: I.TagType)});
489
490 maybeInsertLocation(Loc: I.DefLoc, CDCtx, Obj&: RecordValue);
491
492 SmallString<64> BasePath = I.getRelativeFilePath(CurrentPath: "");
493 extractScopeChildren(S: I.Children, Obj&: RecordValue, ParentInfoDir: BasePath, CDCtx);
494 json::Value PublicMembers = Array();
495 json::Array &PubMemberRef = *PublicMembers.getAsArray();
496 PubMemberRef.reserve(S: I.Members.size());
497 json::Value ProtectedMembers = Array();
498 json::Array &ProtMemberRef = *ProtectedMembers.getAsArray();
499 ProtMemberRef.reserve(S: I.Members.size());
500 json::Value PrivateMembers = Array();
501 json::Array &PrivMemberRef = *PrivateMembers.getAsArray();
502 PrivMemberRef.reserve(S: I.Members.size());
503 for (const MemberTypeInfo &Member : I.Members) {
504 json::Value MemberValue = Object();
505 auto &MVRef = *MemberValue.getAsObject();
506 MVRef.insert(E: {.K: "Name", .V: Member.Name});
507 MVRef.insert(E: {.K: "Type", .V: Member.Type.Name});
508 extractDescriptionFromInfo(Descriptions: Member.Description, EnumValObj&: MVRef);
509
510 if (Member.Access == AccessSpecifier::AS_public)
511 PubMemberRef.emplace_back(A&: MemberValue);
512 else if (Member.Access == AccessSpecifier::AS_protected)
513 ProtMemberRef.emplace_back(A&: MemberValue);
514 else if (Member.Access == AccessSpecifier::AS_private)
515 ProtMemberRef.emplace_back(A&: MemberValue);
516 }
517 if (!PubMemberRef.empty())
518 RecordValue.insert(E: {.K: "PublicMembers", .V: Object{{.K: "Obj", .V: PublicMembers}}});
519 if (!ProtMemberRef.empty())
520 RecordValue.insert(E: {.K: "ProtectedMembers", .V: Object{{.K: "Obj", .V: ProtectedMembers}}});
521 if (!PrivMemberRef.empty())
522 RecordValue.insert(E: {.K: "PrivateMembers", .V: Object{{.K: "Obj", .V: PrivateMembers}}});
523
524 return RecordValue;
525}
526
527static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V,
528 Info *I) {
529 V.getAsObject()->insert(E: {.K: "ProjectName", .V: CDCtx.ProjectName});
530 json::Value StylesheetArr = Array();
531 auto InfoPath = I->getRelativeFilePath(CurrentPath: "");
532 SmallString<128> RelativePath = computeRelativePath(Destination: "", Origin: InfoPath);
533 sys::path::native(path&: RelativePath, style: sys::path::Style::posix);
534
535 auto *SSA = StylesheetArr.getAsArray();
536 SSA->reserve(S: CDCtx.UserStylesheets.size());
537 for (const auto &FilePath : CDCtx.UserStylesheets) {
538 SmallString<128> StylesheetPath = RelativePath;
539 sys::path::append(path&: StylesheetPath, style: sys::path::Style::posix,
540 a: sys::path::filename(path: FilePath));
541 SSA->emplace_back(A&: StylesheetPath);
542 }
543 V.getAsObject()->insert(E: {.K: "Stylesheets", .V: StylesheetArr});
544
545 json::Value ScriptArr = Array();
546 auto *SCA = ScriptArr.getAsArray();
547 SCA->reserve(S: CDCtx.JsScripts.size());
548 for (auto Script : CDCtx.JsScripts) {
549 SmallString<128> JsPath = RelativePath;
550 sys::path::append(path&: JsPath, style: sys::path::Style::posix,
551 a: sys::path::filename(path: Script));
552 SCA->emplace_back(A&: JsPath);
553 }
554 V.getAsObject()->insert(E: {.K: "Scripts", .V: ScriptArr});
555 return Error::success();
556}
557
558Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
559 const ClangDocContext &CDCtx) {
560 switch (I->IT) {
561 case InfoType::IT_namespace: {
562 json::Value V =
563 extractValue(I: *static_cast<clang::doc::NamespaceInfo *>(I), CDCtx);
564 if (auto Err = setupTemplateValue(CDCtx, V, I))
565 return Err;
566 assert(NamespaceTemplate && "NamespaceTemplate is nullptr.");
567 NamespaceTemplate->render(Data: V, OS);
568 break;
569 }
570 case InfoType::IT_record: {
571 json::Value V =
572 extractValue(I: *static_cast<clang::doc::RecordInfo *>(I), CDCtx);
573 if (auto Err = setupTemplateValue(CDCtx, V, I))
574 return Err;
575 // Serialize the JSON value to the output stream in a readable format.
576 RecordTemplate->render(Data: V, OS);
577 break;
578 }
579 case InfoType::IT_enum:
580 OS << "IT_enum\n";
581 break;
582 case InfoType::IT_function:
583 OS << "IT_Function\n";
584 break;
585 case InfoType::IT_typedef:
586 OS << "IT_typedef\n";
587 break;
588 case InfoType::IT_default:
589 return createStringError(EC: inconvertibleErrorCode(), S: "unexpected InfoType");
590 }
591 return Error::success();
592}
593
594Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) {
595 for (const auto &FilePath : CDCtx.UserStylesheets)
596 if (Error Err = copyFile(FilePath, OutDirectory: CDCtx.OutDirectory))
597 return Err;
598 for (const auto &FilePath : CDCtx.JsScripts)
599 if (Error Err = copyFile(FilePath, OutDirectory: CDCtx.OutDirectory))
600 return Err;
601 return Error::success();
602}
603
604const char *MustacheHTMLGenerator::Format = "mustache";
605
606static GeneratorRegistry::Add<MustacheHTMLGenerator>
607 MHTML(MustacheHTMLGenerator::Format, "Generator for mustache HTML output.");
608
609// This anchor is used to force the linker to link in the generated object
610// file and thus register the generator.
611volatile int MHTMLGeneratorAnchorSource = 0;
612
613} // namespace doc
614} // namespace clang
615

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