1//===-- HTMLGenerator.cpp - HTML 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 "clang/Basic/Version.h"
12#include "llvm/ADT/StringExtras.h"
13#include "llvm/ADT/StringRef.h"
14#include "llvm/ADT/StringSet.h"
15#include "llvm/Support/FileSystem.h"
16#include "llvm/Support/JSON.h"
17#include "llvm/Support/Path.h"
18#include "llvm/Support/raw_ostream.h"
19#include <optional>
20#include <string>
21
22using namespace llvm;
23
24namespace clang {
25namespace doc {
26
27namespace {
28
29class HTMLTag {
30public:
31 // Any other tag can be added if required
32 enum TagType {
33 TAG_A,
34 TAG_DIV,
35 TAG_FOOTER,
36 TAG_H1,
37 TAG_H2,
38 TAG_H3,
39 TAG_HEADER,
40 TAG_LI,
41 TAG_LINK,
42 TAG_MAIN,
43 TAG_META,
44 TAG_OL,
45 TAG_P,
46 TAG_SCRIPT,
47 TAG_SPAN,
48 TAG_TITLE,
49 TAG_UL,
50 };
51
52 HTMLTag() = default;
53 constexpr HTMLTag(TagType Value) : Value(Value) {}
54
55 operator TagType() const { return Value; }
56 operator bool() = delete;
57
58 bool IsSelfClosing() const;
59 llvm::SmallString<16> ToString() const;
60
61private:
62 TagType Value;
63};
64
65enum NodeType {
66 NODE_TEXT,
67 NODE_TAG,
68};
69
70struct HTMLNode {
71 HTMLNode(NodeType Type) : Type(Type) {}
72 virtual ~HTMLNode() = default;
73
74 virtual void Render(llvm::raw_ostream &OS, int IndentationLevel) = 0;
75 NodeType Type; // Type of node
76};
77
78struct TextNode : public HTMLNode {
79 TextNode(const Twine &Text)
80 : HTMLNode(NodeType::NODE_TEXT), Text(Text.str()) {}
81
82 std::string Text; // Content of node
83 void Render(llvm::raw_ostream &OS, int IndentationLevel) override;
84};
85
86struct TagNode : public HTMLNode {
87 TagNode(HTMLTag Tag) : HTMLNode(NodeType::NODE_TAG), Tag(Tag) {}
88 TagNode(HTMLTag Tag, const Twine &Text) : TagNode(Tag) {
89 Children.emplace_back(args: std::make_unique<TextNode>(args: Text.str()));
90 }
91
92 HTMLTag Tag; // Name of HTML Tag (p, div, h1)
93 std::vector<std::unique_ptr<HTMLNode>> Children; // List of child nodes
94 std::vector<std::pair<std::string, std::string>>
95 Attributes; // List of key-value attributes for tag
96
97 void Render(llvm::raw_ostream &OS, int IndentationLevel) override;
98};
99
100constexpr const char *kDoctypeDecl = "<!DOCTYPE html>";
101
102struct HTMLFile {
103 std::vector<std::unique_ptr<HTMLNode>> Children; // List of child nodes
104 void Render(llvm::raw_ostream &OS) {
105 OS << kDoctypeDecl << "\n";
106 for (const auto &C : Children) {
107 C->Render(OS, IndentationLevel: 0);
108 OS << "\n";
109 }
110 }
111};
112
113} // namespace
114
115bool HTMLTag::IsSelfClosing() const {
116 switch (Value) {
117 case HTMLTag::TAG_META:
118 case HTMLTag::TAG_LINK:
119 return true;
120 case HTMLTag::TAG_A:
121 case HTMLTag::TAG_DIV:
122 case HTMLTag::TAG_FOOTER:
123 case HTMLTag::TAG_H1:
124 case HTMLTag::TAG_H2:
125 case HTMLTag::TAG_H3:
126 case HTMLTag::TAG_HEADER:
127 case HTMLTag::TAG_LI:
128 case HTMLTag::TAG_MAIN:
129 case HTMLTag::TAG_OL:
130 case HTMLTag::TAG_P:
131 case HTMLTag::TAG_SCRIPT:
132 case HTMLTag::TAG_SPAN:
133 case HTMLTag::TAG_TITLE:
134 case HTMLTag::TAG_UL:
135 return false;
136 }
137 llvm_unreachable("Unhandled HTMLTag::TagType");
138}
139
140llvm::SmallString<16> HTMLTag::ToString() const {
141 switch (Value) {
142 case HTMLTag::TAG_A:
143 return llvm::SmallString<16>("a");
144 case HTMLTag::TAG_DIV:
145 return llvm::SmallString<16>("div");
146 case HTMLTag::TAG_FOOTER:
147 return llvm::SmallString<16>("footer");
148 case HTMLTag::TAG_H1:
149 return llvm::SmallString<16>("h1");
150 case HTMLTag::TAG_H2:
151 return llvm::SmallString<16>("h2");
152 case HTMLTag::TAG_H3:
153 return llvm::SmallString<16>("h3");
154 case HTMLTag::TAG_HEADER:
155 return llvm::SmallString<16>("header");
156 case HTMLTag::TAG_LI:
157 return llvm::SmallString<16>("li");
158 case HTMLTag::TAG_LINK:
159 return llvm::SmallString<16>("link");
160 case HTMLTag::TAG_MAIN:
161 return llvm::SmallString<16>("main");
162 case HTMLTag::TAG_META:
163 return llvm::SmallString<16>("meta");
164 case HTMLTag::TAG_OL:
165 return llvm::SmallString<16>("ol");
166 case HTMLTag::TAG_P:
167 return llvm::SmallString<16>("p");
168 case HTMLTag::TAG_SCRIPT:
169 return llvm::SmallString<16>("script");
170 case HTMLTag::TAG_SPAN:
171 return llvm::SmallString<16>("span");
172 case HTMLTag::TAG_TITLE:
173 return llvm::SmallString<16>("title");
174 case HTMLTag::TAG_UL:
175 return llvm::SmallString<16>("ul");
176 }
177 llvm_unreachable("Unhandled HTMLTag::TagType");
178}
179
180void TextNode::Render(llvm::raw_ostream &OS, int IndentationLevel) {
181 OS.indent(NumSpaces: IndentationLevel * 2);
182 printHTMLEscaped(String: Text, Out&: OS);
183}
184
185void TagNode::Render(llvm::raw_ostream &OS, int IndentationLevel) {
186 // Children nodes are rendered in the same line if all of them are text nodes
187 bool InlineChildren = true;
188 for (const auto &C : Children)
189 if (C->Type == NodeType::NODE_TAG) {
190 InlineChildren = false;
191 break;
192 }
193 OS.indent(NumSpaces: IndentationLevel * 2);
194 OS << "<" << Tag.ToString();
195 for (const auto &A : Attributes)
196 OS << " " << A.first << "=\"" << A.second << "\"";
197 if (Tag.IsSelfClosing()) {
198 OS << "/>";
199 return;
200 }
201 OS << ">";
202 if (!InlineChildren)
203 OS << "\n";
204 bool NewLineRendered = true;
205 for (const auto &C : Children) {
206 int ChildrenIndentation =
207 InlineChildren || !NewLineRendered ? 0 : IndentationLevel + 1;
208 C->Render(OS, IndentationLevel: ChildrenIndentation);
209 if (!InlineChildren && (C == Children.back() ||
210 (C->Type != NodeType::NODE_TEXT ||
211 (&C + 1)->get()->Type != NodeType::NODE_TEXT))) {
212 OS << "\n";
213 NewLineRendered = true;
214 } else
215 NewLineRendered = false;
216 }
217 if (!InlineChildren)
218 OS.indent(NumSpaces: IndentationLevel * 2);
219 OS << "</" << Tag.ToString() << ">";
220}
221
222template <typename Derived, typename Base,
223 typename = std::enable_if<std::is_base_of<Derived, Base>::value>>
224static void AppendVector(std::vector<Derived> &&New,
225 std::vector<Base> &Original) {
226 std::move(New.begin(), New.end(), std::back_inserter(Original));
227}
228
229// Compute the relative path from an Origin directory to a Destination directory
230static SmallString<128> computeRelativePath(StringRef Destination,
231 StringRef Origin) {
232 // If Origin is empty, the relative path to the Destination is its complete
233 // path.
234 if (Origin.empty())
235 return Destination;
236
237 // The relative path is an empty path if both directories are the same.
238 if (Destination == Origin)
239 return {};
240
241 // These iterators iterate through each of their parent directories
242 llvm::sys::path::const_iterator FileI = llvm::sys::path::begin(path: Destination);
243 llvm::sys::path::const_iterator FileE = llvm::sys::path::end(path: Destination);
244 llvm::sys::path::const_iterator DirI = llvm::sys::path::begin(path: Origin);
245 llvm::sys::path::const_iterator DirE = llvm::sys::path::end(path: Origin);
246 // Advance both iterators until the paths differ. Example:
247 // Destination = A/B/C/D
248 // Origin = A/B/E/F
249 // FileI will point to C and DirI to E. The directories behind them is the
250 // directory they share (A/B).
251 while (FileI != FileE && DirI != DirE && *FileI == *DirI) {
252 ++FileI;
253 ++DirI;
254 }
255 SmallString<128> Result; // This will hold the resulting path.
256 // Result has to go up one directory for each of the remaining directories in
257 // Origin
258 while (DirI != DirE) {
259 llvm::sys::path::append(path&: Result, a: "..");
260 ++DirI;
261 }
262 // Result has to append each of the remaining directories in Destination
263 while (FileI != FileE) {
264 llvm::sys::path::append(path&: Result, a: *FileI);
265 ++FileI;
266 }
267 return Result;
268}
269
270// HTML generation
271
272static std::vector<std::unique_ptr<TagNode>>
273genStylesheetsHTML(StringRef InfoPath, const ClangDocContext &CDCtx) {
274 std::vector<std::unique_ptr<TagNode>> Out;
275 for (const auto &FilePath : CDCtx.UserStylesheets) {
276 auto LinkNode = std::make_unique<TagNode>(args: HTMLTag::TAG_LINK);
277 LinkNode->Attributes.emplace_back(args: "rel", args: "stylesheet");
278 SmallString<128> StylesheetPath = computeRelativePath(Destination: "", Origin: InfoPath);
279 llvm::sys::path::append(path&: StylesheetPath,
280 a: llvm::sys::path::filename(path: FilePath));
281 // Paths in HTML must be in posix-style
282 llvm::sys::path::native(path&: StylesheetPath, style: llvm::sys::path::Style::posix);
283 LinkNode->Attributes.emplace_back(args: "href", args: std::string(StylesheetPath));
284 Out.emplace_back(args: std::move(LinkNode));
285 }
286 return Out;
287}
288
289static std::vector<std::unique_ptr<TagNode>>
290genJsScriptsHTML(StringRef InfoPath, const ClangDocContext &CDCtx) {
291 std::vector<std::unique_ptr<TagNode>> Out;
292 for (const auto &FilePath : CDCtx.JsScripts) {
293 auto ScriptNode = std::make_unique<TagNode>(args: HTMLTag::TAG_SCRIPT);
294 SmallString<128> ScriptPath = computeRelativePath(Destination: "", Origin: InfoPath);
295 llvm::sys::path::append(path&: ScriptPath, a: llvm::sys::path::filename(path: FilePath));
296 // Paths in HTML must be in posix-style
297 llvm::sys::path::native(path&: ScriptPath, style: llvm::sys::path::Style::posix);
298 ScriptNode->Attributes.emplace_back(args: "src", args: std::string(ScriptPath));
299 Out.emplace_back(args: std::move(ScriptNode));
300 }
301 return Out;
302}
303
304static std::unique_ptr<TagNode> genLink(const Twine &Text, const Twine &Link) {
305 auto LinkNode = std::make_unique<TagNode>(args: HTMLTag::TAG_A, args: Text);
306 LinkNode->Attributes.emplace_back(args: "href", args: Link.str());
307 return LinkNode;
308}
309
310static std::unique_ptr<HTMLNode>
311genReference(const Reference &Type, StringRef CurrentDirectory,
312 std::optional<StringRef> JumpToSection = std::nullopt) {
313 if (Type.Path.empty()) {
314 if (!JumpToSection)
315 return std::make_unique<TextNode>(args: Type.Name);
316 else
317 return genLink(Text: Type.Name, Link: "#" + *JumpToSection);
318 }
319 llvm::SmallString<64> Path = Type.getRelativeFilePath(CurrentPath: CurrentDirectory);
320 llvm::sys::path::append(path&: Path, a: Type.getFileBaseName() + ".html");
321
322 // Paths in HTML must be in posix-style
323 llvm::sys::path::native(path&: Path, style: llvm::sys::path::Style::posix);
324 if (JumpToSection)
325 Path += ("#" + *JumpToSection).str();
326 return genLink(Text: Type.Name, Link: Path);
327}
328
329static std::vector<std::unique_ptr<HTMLNode>>
330genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs,
331 const StringRef &CurrentDirectory) {
332 std::vector<std::unique_ptr<HTMLNode>> Out;
333 for (const auto &R : Refs) {
334 if (&R != Refs.begin())
335 Out.emplace_back(args: std::make_unique<TextNode>(args: ", "));
336 Out.emplace_back(args: genReference(Type: R, CurrentDirectory));
337 }
338 return Out;
339}
340
341static std::vector<std::unique_ptr<TagNode>>
342genHTML(const EnumInfo &I, const ClangDocContext &CDCtx);
343static std::vector<std::unique_ptr<TagNode>>
344genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
345 StringRef ParentInfoDir);
346
347static std::vector<std::unique_ptr<TagNode>>
348genEnumsBlock(const std::vector<EnumInfo> &Enums,
349 const ClangDocContext &CDCtx) {
350 if (Enums.empty())
351 return {};
352
353 std::vector<std::unique_ptr<TagNode>> Out;
354 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_H2, args: "Enums"));
355 Out.back()->Attributes.emplace_back(args: "id", args: "Enums");
356 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_DIV));
357 auto &DivBody = Out.back();
358 for (const auto &E : Enums) {
359 std::vector<std::unique_ptr<TagNode>> Nodes = genHTML(I: E, CDCtx);
360 AppendVector(New: std::move(Nodes), Original&: DivBody->Children);
361 }
362 return Out;
363}
364
365static std::unique_ptr<TagNode>
366genEnumMembersBlock(const llvm::SmallVector<EnumValueInfo, 4> &Members) {
367 if (Members.empty())
368 return nullptr;
369
370 auto List = std::make_unique<TagNode>(args: HTMLTag::TAG_UL);
371 for (const auto &M : Members)
372 List->Children.emplace_back(
373 args: std::make_unique<TagNode>(args: HTMLTag::TAG_LI, args: M.Name));
374 return List;
375}
376
377static std::vector<std::unique_ptr<TagNode>>
378genFunctionsBlock(const std::vector<FunctionInfo> &Functions,
379 const ClangDocContext &CDCtx, StringRef ParentInfoDir) {
380 if (Functions.empty())
381 return {};
382
383 std::vector<std::unique_ptr<TagNode>> Out;
384 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_H2, args: "Functions"));
385 Out.back()->Attributes.emplace_back(args: "id", args: "Functions");
386 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_DIV));
387 auto &DivBody = Out.back();
388 for (const auto &F : Functions) {
389 std::vector<std::unique_ptr<TagNode>> Nodes =
390 genHTML(I: F, CDCtx, ParentInfoDir);
391 AppendVector(New: std::move(Nodes), Original&: DivBody->Children);
392 }
393 return Out;
394}
395
396static std::vector<std::unique_ptr<TagNode>>
397genRecordMembersBlock(const llvm::SmallVector<MemberTypeInfo, 4> &Members,
398 StringRef ParentInfoDir) {
399 if (Members.empty())
400 return {};
401
402 std::vector<std::unique_ptr<TagNode>> Out;
403 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_H2, args: "Members"));
404 Out.back()->Attributes.emplace_back(args: "id", args: "Members");
405 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_UL));
406 auto &ULBody = Out.back();
407 for (const auto &M : Members) {
408 std::string Access = getAccessSpelling(AS: M.Access).str();
409 if (Access != "")
410 Access = Access + " ";
411 auto LIBody = std::make_unique<TagNode>(args: HTMLTag::TAG_LI);
412 LIBody->Children.emplace_back(args: std::make_unique<TextNode>(args&: Access));
413 LIBody->Children.emplace_back(args: genReference(Type: M.Type, CurrentDirectory: ParentInfoDir));
414 LIBody->Children.emplace_back(args: std::make_unique<TextNode>(args: " " + M.Name));
415 ULBody->Children.emplace_back(args: std::move(LIBody));
416 }
417 return Out;
418}
419
420static std::vector<std::unique_ptr<TagNode>>
421genReferencesBlock(const std::vector<Reference> &References,
422 llvm::StringRef Title, StringRef ParentPath) {
423 if (References.empty())
424 return {};
425
426 std::vector<std::unique_ptr<TagNode>> Out;
427 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_H2, args&: Title));
428 Out.back()->Attributes.emplace_back(args: "id", args: std::string(Title));
429 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_UL));
430 auto &ULBody = Out.back();
431 for (const auto &R : References) {
432 auto LiNode = std::make_unique<TagNode>(args: HTMLTag::TAG_LI);
433 LiNode->Children.emplace_back(args: genReference(Type: R, CurrentDirectory: ParentPath));
434 ULBody->Children.emplace_back(args: std::move(LiNode));
435 }
436 return Out;
437}
438
439static std::unique_ptr<TagNode>
440writeFileDefinition(const Location &L,
441 std::optional<StringRef> RepositoryUrl = std::nullopt) {
442 if (!L.IsFileInRootDir || !RepositoryUrl)
443 return std::make_unique<TagNode>(
444 args: HTMLTag::TAG_P, args: "Defined at line " + std::to_string(val: L.LineNumber) +
445 " of file " + L.Filename);
446 SmallString<128> FileURL(*RepositoryUrl);
447 llvm::sys::path::append(path&: FileURL, style: llvm::sys::path::Style::posix, a: L.Filename);
448 auto Node = std::make_unique<TagNode>(args: HTMLTag::TAG_P);
449 Node->Children.emplace_back(args: std::make_unique<TextNode>(args: "Defined at line "));
450 auto LocNumberNode =
451 std::make_unique<TagNode>(args: HTMLTag::TAG_A, args: std::to_string(val: L.LineNumber));
452 // The links to a specific line in the source code use the github /
453 // googlesource notation so it won't work for all hosting pages.
454 LocNumberNode->Attributes.emplace_back(
455 args: "href", args: (FileURL + "#" + std::to_string(val: L.LineNumber)).str());
456 Node->Children.emplace_back(args: std::move(LocNumberNode));
457 Node->Children.emplace_back(args: std::make_unique<TextNode>(args: " of file "));
458 auto LocFileNode = std::make_unique<TagNode>(
459 args: HTMLTag::TAG_A, args: llvm::sys::path::filename(path: FileURL));
460 LocFileNode->Attributes.emplace_back(args: "href", args: std::string(FileURL));
461 Node->Children.emplace_back(args: std::move(LocFileNode));
462 return Node;
463}
464
465static std::vector<std::unique_ptr<TagNode>>
466genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList);
467
468// Generates a list of child nodes for the HTML head tag
469// It contains a meta node, link nodes to import CSS files, and script nodes to
470// import JS files
471static std::vector<std::unique_ptr<TagNode>>
472genFileHeadNodes(StringRef Title, StringRef InfoPath,
473 const ClangDocContext &CDCtx) {
474 std::vector<std::unique_ptr<TagNode>> Out;
475 auto MetaNode = std::make_unique<TagNode>(args: HTMLTag::TAG_META);
476 MetaNode->Attributes.emplace_back(args: "charset", args: "utf-8");
477 Out.emplace_back(args: std::move(MetaNode));
478 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_TITLE, args&: Title));
479 std::vector<std::unique_ptr<TagNode>> StylesheetsNodes =
480 genStylesheetsHTML(InfoPath, CDCtx);
481 AppendVector(New: std::move(StylesheetsNodes), Original&: Out);
482 std::vector<std::unique_ptr<TagNode>> JsNodes =
483 genJsScriptsHTML(InfoPath, CDCtx);
484 AppendVector(New: std::move(JsNodes), Original&: Out);
485 return Out;
486}
487
488// Generates a header HTML node that can be used for any file
489// It contains the project name
490static std::unique_ptr<TagNode> genFileHeaderNode(StringRef ProjectName) {
491 auto HeaderNode = std::make_unique<TagNode>(args: HTMLTag::TAG_HEADER, args&: ProjectName);
492 HeaderNode->Attributes.emplace_back(args: "id", args: "project-title");
493 return HeaderNode;
494}
495
496// Generates a main HTML node that has all the main content of an info file
497// It contains both indexes and the info's documented information
498// This function should only be used for the info files (not for the file that
499// only has the general index)
500static std::unique_ptr<TagNode> genInfoFileMainNode(
501 StringRef InfoPath,
502 std::vector<std::unique_ptr<TagNode>> &MainContentInnerNodes,
503 const Index &InfoIndex) {
504 auto MainNode = std::make_unique<TagNode>(args: HTMLTag::TAG_MAIN);
505
506 auto LeftSidebarNode = std::make_unique<TagNode>(args: HTMLTag::TAG_DIV);
507 LeftSidebarNode->Attributes.emplace_back(args: "id", args: "sidebar-left");
508 LeftSidebarNode->Attributes.emplace_back(args: "path", args: std::string(InfoPath));
509 LeftSidebarNode->Attributes.emplace_back(
510 args: "class", args: "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left");
511
512 auto MainContentNode = std::make_unique<TagNode>(args: HTMLTag::TAG_DIV);
513 MainContentNode->Attributes.emplace_back(args: "id", args: "main-content");
514 MainContentNode->Attributes.emplace_back(
515 args: "class", args: "col-xs-12 col-sm-9 col-md-8 main-content");
516 AppendVector(New: std::move(MainContentInnerNodes), Original&: MainContentNode->Children);
517
518 auto RightSidebarNode = std::make_unique<TagNode>(args: HTMLTag::TAG_DIV);
519 RightSidebarNode->Attributes.emplace_back(args: "id", args: "sidebar-right");
520 RightSidebarNode->Attributes.emplace_back(
521 args: "class", args: "col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right");
522 std::vector<std::unique_ptr<TagNode>> InfoIndexHTML =
523 genHTML(Index: InfoIndex, InfoPath, IsOutermostList: true);
524 AppendVector(New: std::move(InfoIndexHTML), Original&: RightSidebarNode->Children);
525
526 MainNode->Children.emplace_back(args: std::move(LeftSidebarNode));
527 MainNode->Children.emplace_back(args: std::move(MainContentNode));
528 MainNode->Children.emplace_back(args: std::move(RightSidebarNode));
529
530 return MainNode;
531}
532
533// Generates a footer HTML node that can be used for any file
534// It contains clang-doc's version
535static std::unique_ptr<TagNode> genFileFooterNode() {
536 auto FooterNode = std::make_unique<TagNode>(args: HTMLTag::TAG_FOOTER);
537 auto SpanNode = std::make_unique<TagNode>(
538 args: HTMLTag::TAG_SPAN, args: clang::getClangToolFullVersion(ToolName: "clang-doc"));
539 SpanNode->Attributes.emplace_back(args: "class", args: "no-break");
540 FooterNode->Children.emplace_back(args: std::move(SpanNode));
541 return FooterNode;
542}
543
544// Generates a complete HTMLFile for an Info
545static HTMLFile
546genInfoFile(StringRef Title, StringRef InfoPath,
547 std::vector<std::unique_ptr<TagNode>> &MainContentNodes,
548 const Index &InfoIndex, const ClangDocContext &CDCtx) {
549 HTMLFile F;
550
551 std::vector<std::unique_ptr<TagNode>> HeadNodes =
552 genFileHeadNodes(Title, InfoPath, CDCtx);
553 std::unique_ptr<TagNode> HeaderNode = genFileHeaderNode(ProjectName: CDCtx.ProjectName);
554 std::unique_ptr<TagNode> MainNode =
555 genInfoFileMainNode(InfoPath, MainContentInnerNodes&: MainContentNodes, InfoIndex);
556 std::unique_ptr<TagNode> FooterNode = genFileFooterNode();
557
558 AppendVector(New: std::move(HeadNodes), Original&: F.Children);
559 F.Children.emplace_back(args: std::move(HeaderNode));
560 F.Children.emplace_back(args: std::move(MainNode));
561 F.Children.emplace_back(args: std::move(FooterNode));
562
563 return F;
564}
565
566template <typename T,
567 typename = std::enable_if<std::is_base_of<T, Info>::value>>
568static Index genInfoIndexItem(const std::vector<T> &Infos, StringRef Title) {
569 Index Idx(Title, Title);
570 for (const auto &C : Infos)
571 Idx.Children.emplace_back(C.extractName(),
572 llvm::toHex(llvm::toStringRef(C.USR)));
573 return Idx;
574}
575
576static std::vector<std::unique_ptr<TagNode>>
577genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList) {
578 std::vector<std::unique_ptr<TagNode>> Out;
579 if (!Index.Name.empty()) {
580 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_SPAN));
581 auto &SpanBody = Out.back();
582 if (!Index.JumpToSection)
583 SpanBody->Children.emplace_back(args: genReference(Type: Index, CurrentDirectory: InfoPath));
584 else
585 SpanBody->Children.emplace_back(
586 args: genReference(Type: Index, CurrentDirectory: InfoPath, JumpToSection: Index.JumpToSection->str()));
587 }
588 if (Index.Children.empty())
589 return Out;
590 // Only the outermost list should use ol, the others should use ul
591 HTMLTag ListHTMLTag = IsOutermostList ? HTMLTag::TAG_OL : HTMLTag::TAG_UL;
592 Out.emplace_back(args: std::make_unique<TagNode>(args&: ListHTMLTag));
593 const auto &UlBody = Out.back();
594 for (const auto &C : Index.Children) {
595 auto LiBody = std::make_unique<TagNode>(args: HTMLTag::TAG_LI);
596 std::vector<std::unique_ptr<TagNode>> Nodes = genHTML(Index: C, InfoPath, IsOutermostList: false);
597 AppendVector(New: std::move(Nodes), Original&: LiBody->Children);
598 UlBody->Children.emplace_back(args: std::move(LiBody));
599 }
600 return Out;
601}
602
603static std::unique_ptr<HTMLNode> genHTML(const CommentInfo &I) {
604 if (I.Kind == "FullComment") {
605 auto FullComment = std::make_unique<TagNode>(args: HTMLTag::TAG_DIV);
606 for (const auto &Child : I.Children) {
607 std::unique_ptr<HTMLNode> Node = genHTML(I: *Child);
608 if (Node)
609 FullComment->Children.emplace_back(args: std::move(Node));
610 }
611 return std::move(FullComment);
612 } else if (I.Kind == "ParagraphComment") {
613 auto ParagraphComment = std::make_unique<TagNode>(args: HTMLTag::TAG_P);
614 for (const auto &Child : I.Children) {
615 std::unique_ptr<HTMLNode> Node = genHTML(I: *Child);
616 if (Node)
617 ParagraphComment->Children.emplace_back(args: std::move(Node));
618 }
619 if (ParagraphComment->Children.empty())
620 return nullptr;
621 return std::move(ParagraphComment);
622 } else if (I.Kind == "TextComment") {
623 if (I.Text == "")
624 return nullptr;
625 return std::make_unique<TextNode>(args: I.Text);
626 }
627 return nullptr;
628}
629
630static std::unique_ptr<TagNode> genHTML(const std::vector<CommentInfo> &C) {
631 auto CommentBlock = std::make_unique<TagNode>(args: HTMLTag::TAG_DIV);
632 for (const auto &Child : C) {
633 if (std::unique_ptr<HTMLNode> Node = genHTML(I: Child))
634 CommentBlock->Children.emplace_back(args: std::move(Node));
635 }
636 return CommentBlock;
637}
638
639static std::vector<std::unique_ptr<TagNode>>
640genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
641 std::vector<std::unique_ptr<TagNode>> Out;
642 std::string EnumType;
643 if (I.Scoped)
644 EnumType = "enum class ";
645 else
646 EnumType = "enum ";
647
648 Out.emplace_back(
649 args: std::make_unique<TagNode>(args: HTMLTag::TAG_H3, args: EnumType + I.Name));
650 Out.back()->Attributes.emplace_back(args: "id",
651 args: llvm::toHex(Input: llvm::toStringRef(Input: I.USR)));
652
653 std::unique_ptr<TagNode> Node = genEnumMembersBlock(Members: I.Members);
654 if (Node)
655 Out.emplace_back(args: std::move(Node));
656
657 if (I.DefLoc) {
658 if (!CDCtx.RepositoryUrl)
659 Out.emplace_back(args: writeFileDefinition(L: *I.DefLoc));
660 else
661 Out.emplace_back(args: writeFileDefinition(
662 L: *I.DefLoc, RepositoryUrl: StringRef{*CDCtx.RepositoryUrl}));
663 }
664
665 std::string Description;
666 if (!I.Description.empty())
667 Out.emplace_back(args: genHTML(C: I.Description));
668
669 return Out;
670}
671
672static std::vector<std::unique_ptr<TagNode>>
673genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
674 StringRef ParentInfoDir) {
675 std::vector<std::unique_ptr<TagNode>> Out;
676 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_H3, args: I.Name));
677 // USR is used as id for functions instead of name to disambiguate function
678 // overloads.
679 Out.back()->Attributes.emplace_back(args: "id",
680 args: llvm::toHex(Input: llvm::toStringRef(Input: I.USR)));
681
682 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_P));
683 auto &FunctionHeader = Out.back();
684
685 std::string Access = getAccessSpelling(AS: I.Access).str();
686 if (Access != "")
687 FunctionHeader->Children.emplace_back(
688 args: std::make_unique<TextNode>(args: Access + " "));
689 if (I.ReturnType.Type.Name != "") {
690 FunctionHeader->Children.emplace_back(
691 args: genReference(Type: I.ReturnType.Type, CurrentDirectory: ParentInfoDir));
692 FunctionHeader->Children.emplace_back(args: std::make_unique<TextNode>(args: " "));
693 }
694 FunctionHeader->Children.emplace_back(
695 args: std::make_unique<TextNode>(args: I.Name + "("));
696
697 for (const auto &P : I.Params) {
698 if (&P != I.Params.begin())
699 FunctionHeader->Children.emplace_back(args: std::make_unique<TextNode>(args: ", "));
700 FunctionHeader->Children.emplace_back(args: genReference(Type: P.Type, CurrentDirectory: ParentInfoDir));
701 FunctionHeader->Children.emplace_back(
702 args: std::make_unique<TextNode>(args: " " + P.Name));
703 }
704 FunctionHeader->Children.emplace_back(args: std::make_unique<TextNode>(args: ")"));
705
706 if (I.DefLoc) {
707 if (!CDCtx.RepositoryUrl)
708 Out.emplace_back(args: writeFileDefinition(L: *I.DefLoc));
709 else
710 Out.emplace_back(args: writeFileDefinition(
711 L: *I.DefLoc, RepositoryUrl: StringRef{*CDCtx.RepositoryUrl}));
712 }
713
714 std::string Description;
715 if (!I.Description.empty())
716 Out.emplace_back(args: genHTML(C: I.Description));
717
718 return Out;
719}
720
721static std::vector<std::unique_ptr<TagNode>>
722genHTML(const NamespaceInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx,
723 std::string &InfoTitle) {
724 std::vector<std::unique_ptr<TagNode>> Out;
725 if (I.Name.str() == "")
726 InfoTitle = "Global Namespace";
727 else
728 InfoTitle = ("namespace " + I.Name).str();
729
730 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_H1, args&: InfoTitle));
731
732 std::string Description;
733 if (!I.Description.empty())
734 Out.emplace_back(args: genHTML(C: I.Description));
735
736 llvm::SmallString<64> BasePath = I.getRelativeFilePath(CurrentPath: "");
737
738 std::vector<std::unique_ptr<TagNode>> ChildNamespaces =
739 genReferencesBlock(References: I.Children.Namespaces, Title: "Namespaces", ParentPath: BasePath);
740 AppendVector(New: std::move(ChildNamespaces), Original&: Out);
741 std::vector<std::unique_ptr<TagNode>> ChildRecords =
742 genReferencesBlock(References: I.Children.Records, Title: "Records", ParentPath: BasePath);
743 AppendVector(New: std::move(ChildRecords), Original&: Out);
744
745 std::vector<std::unique_ptr<TagNode>> ChildFunctions =
746 genFunctionsBlock(Functions: I.Children.Functions, CDCtx, ParentInfoDir: BasePath);
747 AppendVector(New: std::move(ChildFunctions), Original&: Out);
748 std::vector<std::unique_ptr<TagNode>> ChildEnums =
749 genEnumsBlock(Enums: I.Children.Enums, CDCtx);
750 AppendVector(New: std::move(ChildEnums), Original&: Out);
751
752 if (!I.Children.Namespaces.empty())
753 InfoIndex.Children.emplace_back(args: "Namespaces", args: "Namespaces");
754 if (!I.Children.Records.empty())
755 InfoIndex.Children.emplace_back(args: "Records", args: "Records");
756 if (!I.Children.Functions.empty())
757 InfoIndex.Children.emplace_back(
758 args: genInfoIndexItem(Infos: I.Children.Functions, Title: "Functions"));
759 if (!I.Children.Enums.empty())
760 InfoIndex.Children.emplace_back(
761 args: genInfoIndexItem(Infos: I.Children.Enums, Title: "Enums"));
762
763 return Out;
764}
765
766static std::vector<std::unique_ptr<TagNode>>
767genHTML(const RecordInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx,
768 std::string &InfoTitle) {
769 std::vector<std::unique_ptr<TagNode>> Out;
770 InfoTitle = (getTagType(AS: I.TagType) + " " + I.Name).str();
771 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_H1, args&: InfoTitle));
772
773 if (I.DefLoc) {
774 if (!CDCtx.RepositoryUrl)
775 Out.emplace_back(args: writeFileDefinition(L: *I.DefLoc));
776 else
777 Out.emplace_back(args: writeFileDefinition(
778 L: *I.DefLoc, RepositoryUrl: StringRef{*CDCtx.RepositoryUrl}));
779 }
780
781 std::string Description;
782 if (!I.Description.empty())
783 Out.emplace_back(args: genHTML(C: I.Description));
784
785 std::vector<std::unique_ptr<HTMLNode>> Parents =
786 genReferenceList(Refs: I.Parents, CurrentDirectory: I.Path);
787 std::vector<std::unique_ptr<HTMLNode>> VParents =
788 genReferenceList(Refs: I.VirtualParents, CurrentDirectory: I.Path);
789 if (!Parents.empty() || !VParents.empty()) {
790 Out.emplace_back(args: std::make_unique<TagNode>(args: HTMLTag::TAG_P));
791 auto &PBody = Out.back();
792 PBody->Children.emplace_back(args: std::make_unique<TextNode>(args: "Inherits from "));
793 if (Parents.empty())
794 AppendVector(New: std::move(VParents), Original&: PBody->Children);
795 else if (VParents.empty())
796 AppendVector(New: std::move(Parents), Original&: PBody->Children);
797 else {
798 AppendVector(New: std::move(Parents), Original&: PBody->Children);
799 PBody->Children.emplace_back(args: std::make_unique<TextNode>(args: ", "));
800 AppendVector(New: std::move(VParents), Original&: PBody->Children);
801 }
802 }
803
804 std::vector<std::unique_ptr<TagNode>> Members =
805 genRecordMembersBlock(Members: I.Members, ParentInfoDir: I.Path);
806 AppendVector(New: std::move(Members), Original&: Out);
807 std::vector<std::unique_ptr<TagNode>> ChildRecords =
808 genReferencesBlock(References: I.Children.Records, Title: "Records", ParentPath: I.Path);
809 AppendVector(New: std::move(ChildRecords), Original&: Out);
810
811 std::vector<std::unique_ptr<TagNode>> ChildFunctions =
812 genFunctionsBlock(Functions: I.Children.Functions, CDCtx, ParentInfoDir: I.Path);
813 AppendVector(New: std::move(ChildFunctions), Original&: Out);
814 std::vector<std::unique_ptr<TagNode>> ChildEnums =
815 genEnumsBlock(Enums: I.Children.Enums, CDCtx);
816 AppendVector(New: std::move(ChildEnums), Original&: Out);
817
818 if (!I.Members.empty())
819 InfoIndex.Children.emplace_back(args: "Members", args: "Members");
820 if (!I.Children.Records.empty())
821 InfoIndex.Children.emplace_back(args: "Records", args: "Records");
822 if (!I.Children.Functions.empty())
823 InfoIndex.Children.emplace_back(
824 args: genInfoIndexItem(Infos: I.Children.Functions, Title: "Functions"));
825 if (!I.Children.Enums.empty())
826 InfoIndex.Children.emplace_back(
827 args: genInfoIndexItem(Infos: I.Children.Enums, Title: "Enums"));
828
829 return Out;
830}
831
832static std::vector<std::unique_ptr<TagNode>>
833genHTML(const TypedefInfo &I, const ClangDocContext &CDCtx,
834 std::string &InfoTitle) {
835 // TODO support typedefs in HTML.
836 return {};
837}
838
839/// Generator for HTML documentation.
840class HTMLGenerator : public Generator {
841public:
842 static const char *Format;
843
844 llvm::Error generateDocs(StringRef RootDir,
845 llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
846 const ClangDocContext &CDCtx) override;
847 llvm::Error createResources(ClangDocContext &CDCtx) override;
848 llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
849 const ClangDocContext &CDCtx) override;
850};
851
852const char *HTMLGenerator::Format = "html";
853
854llvm::Error
855HTMLGenerator::generateDocs(StringRef RootDir,
856 llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
857 const ClangDocContext &CDCtx) {
858 // Track which directories we already tried to create.
859 llvm::StringSet<> CreatedDirs;
860
861 // Collect all output by file name and create the nexessary directories.
862 llvm::StringMap<std::vector<doc::Info *>> FileToInfos;
863 for (const auto &Group : Infos) {
864 doc::Info *Info = Group.getValue().get();
865
866 llvm::SmallString<128> Path;
867 llvm::sys::path::native(path: RootDir, result&: Path);
868 llvm::sys::path::append(path&: Path, a: Info->getRelativeFilePath(CurrentPath: ""));
869 if (!CreatedDirs.contains(key: Path)) {
870 if (std::error_code Err = llvm::sys::fs::create_directories(path: Path);
871 Err != std::error_code()) {
872 return llvm::createStringError(EC: Err, Fmt: "Failed to create directory '%s'.",
873 Vals: Path.c_str());
874 }
875 CreatedDirs.insert(key: Path);
876 }
877
878 llvm::sys::path::append(path&: Path, a: Info->getFileBaseName() + ".html");
879 FileToInfos[Path].push_back(x: Info);
880 }
881
882 for (const auto &Group : FileToInfos) {
883 std::error_code FileErr;
884 llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,
885 llvm::sys::fs::OF_None);
886 if (FileErr) {
887 return llvm::createStringError(EC: FileErr, Fmt: "Error opening file '%s'",
888 Vals: Group.getKey().str().c_str());
889 }
890
891 // TODO: https://github.com/llvm/llvm-project/issues/59073
892 // If there are multiple Infos for this file name (for example, template
893 // specializations), this will generate multiple complete web pages (with
894 // <DOCTYPE> and <title>, etc.) concatenated together. This generator needs
895 // some refactoring to be able to output the headers separately from the
896 // contents.
897 for (const auto &Info : Group.getValue()) {
898 if (llvm::Error Err = generateDocForInfo(I: Info, OS&: InfoOS, CDCtx)) {
899 return Err;
900 }
901 }
902 }
903
904 return llvm::Error::success();
905}
906
907llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
908 const ClangDocContext &CDCtx) {
909 std::string InfoTitle;
910 std::vector<std::unique_ptr<TagNode>> MainContentNodes;
911 Index InfoIndex;
912 switch (I->IT) {
913 case InfoType::IT_namespace:
914 MainContentNodes = genHTML(I: *static_cast<clang::doc::NamespaceInfo *>(I),
915 InfoIndex, CDCtx, InfoTitle);
916 break;
917 case InfoType::IT_record:
918 MainContentNodes = genHTML(I: *static_cast<clang::doc::RecordInfo *>(I),
919 InfoIndex, CDCtx, InfoTitle);
920 break;
921 case InfoType::IT_enum:
922 MainContentNodes = genHTML(I: *static_cast<clang::doc::EnumInfo *>(I), CDCtx);
923 break;
924 case InfoType::IT_function:
925 MainContentNodes =
926 genHTML(I: *static_cast<clang::doc::FunctionInfo *>(I), CDCtx, ParentInfoDir: "");
927 break;
928 case InfoType::IT_typedef:
929 MainContentNodes =
930 genHTML(I: *static_cast<clang::doc::TypedefInfo *>(I), CDCtx, InfoTitle);
931 break;
932 case InfoType::IT_default:
933 return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),
934 Msg: "unexpected info type");
935 }
936
937 HTMLFile F = genInfoFile(Title: InfoTitle, InfoPath: I->getRelativeFilePath(CurrentPath: ""),
938 MainContentNodes, InfoIndex, CDCtx);
939 F.Render(OS);
940
941 return llvm::Error::success();
942}
943
944static std::string getRefType(InfoType IT) {
945 switch (IT) {
946 case InfoType::IT_default:
947 return "default";
948 case InfoType::IT_namespace:
949 return "namespace";
950 case InfoType::IT_record:
951 return "record";
952 case InfoType::IT_function:
953 return "function";
954 case InfoType::IT_enum:
955 return "enum";
956 case InfoType::IT_typedef:
957 return "typedef";
958 }
959 llvm_unreachable("Unknown InfoType");
960}
961
962static llvm::Error SerializeIndex(ClangDocContext &CDCtx) {
963 std::error_code OK;
964 std::error_code FileErr;
965 llvm::SmallString<128> FilePath;
966 llvm::sys::path::native(path: CDCtx.OutDirectory, result&: FilePath);
967 llvm::sys::path::append(path&: FilePath, a: "index_json.js");
968 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
969 if (FileErr != OK) {
970 return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),
971 S: "error creating index file: " +
972 FileErr.message());
973 }
974 CDCtx.Idx.sort();
975 llvm::json::OStream J(OS, 2);
976 std::function<void(Index)> IndexToJSON = [&](const Index &I) {
977 J.object(Contents: [&] {
978 J.attribute(Key: "USR", Contents: toHex(Input: llvm::toStringRef(Input: I.USR)));
979 J.attribute(Key: "Name", Contents: I.Name);
980 J.attribute(Key: "RefType", Contents: getRefType(IT: I.RefType));
981 J.attribute(Key: "Path", Contents: I.getRelativeFilePath(CurrentPath: ""));
982 J.attributeArray(Key: "Children", Contents: [&] {
983 for (const Index &C : I.Children)
984 IndexToJSON(C);
985 });
986 });
987 };
988 OS << "var JsonIndex = `\n";
989 IndexToJSON(CDCtx.Idx);
990 OS << "`;\n";
991 return llvm::Error::success();
992}
993
994// Generates a main HTML node that has the main content of the file that shows
995// only the general index
996// It contains the general index with links to all the generated files
997static std::unique_ptr<TagNode> genIndexFileMainNode() {
998 auto MainNode = std::make_unique<TagNode>(args: HTMLTag::TAG_MAIN);
999
1000 auto LeftSidebarNode = std::make_unique<TagNode>(args: HTMLTag::TAG_DIV);
1001 LeftSidebarNode->Attributes.emplace_back(args: "id", args: "sidebar-left");
1002 LeftSidebarNode->Attributes.emplace_back(args: "path", args: "");
1003 LeftSidebarNode->Attributes.emplace_back(
1004 args: "class", args: "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left");
1005 LeftSidebarNode->Attributes.emplace_back(args: "style", args: "flex: 0 100%;");
1006
1007 MainNode->Children.emplace_back(args: std::move(LeftSidebarNode));
1008
1009 return MainNode;
1010}
1011
1012static llvm::Error GenIndex(const ClangDocContext &CDCtx) {
1013 std::error_code FileErr, OK;
1014 llvm::SmallString<128> IndexPath;
1015 llvm::sys::path::native(path: CDCtx.OutDirectory, result&: IndexPath);
1016 llvm::sys::path::append(path&: IndexPath, a: "index.html");
1017 llvm::raw_fd_ostream IndexOS(IndexPath, FileErr, llvm::sys::fs::OF_None);
1018 if (FileErr != OK) {
1019 return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),
1020 S: "error creating main index: " +
1021 FileErr.message());
1022 }
1023
1024 HTMLFile F;
1025
1026 std::vector<std::unique_ptr<TagNode>> HeadNodes =
1027 genFileHeadNodes(Title: "Index", InfoPath: "", CDCtx);
1028 std::unique_ptr<TagNode> HeaderNode = genFileHeaderNode(ProjectName: CDCtx.ProjectName);
1029 std::unique_ptr<TagNode> MainNode = genIndexFileMainNode();
1030 std::unique_ptr<TagNode> FooterNode = genFileFooterNode();
1031
1032 AppendVector(New: std::move(HeadNodes), Original&: F.Children);
1033 F.Children.emplace_back(args: std::move(HeaderNode));
1034 F.Children.emplace_back(args: std::move(MainNode));
1035 F.Children.emplace_back(args: std::move(FooterNode));
1036
1037 F.Render(OS&: IndexOS);
1038
1039 return llvm::Error::success();
1040}
1041
1042static llvm::Error CopyFile(StringRef FilePath, StringRef OutDirectory) {
1043 llvm::SmallString<128> PathWrite;
1044 llvm::sys::path::native(path: OutDirectory, result&: PathWrite);
1045 llvm::sys::path::append(path&: PathWrite, a: llvm::sys::path::filename(path: FilePath));
1046 llvm::SmallString<128> PathRead;
1047 llvm::sys::path::native(path: FilePath, result&: PathRead);
1048 std::error_code OK;
1049 std::error_code FileErr = llvm::sys::fs::copy_file(From: PathRead, To: PathWrite);
1050 if (FileErr != OK) {
1051 return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),
1052 S: "error creating file " +
1053 llvm::sys::path::filename(path: FilePath) +
1054 ": " + FileErr.message() + "\n");
1055 }
1056 return llvm::Error::success();
1057}
1058
1059llvm::Error HTMLGenerator::createResources(ClangDocContext &CDCtx) {
1060 auto Err = SerializeIndex(CDCtx);
1061 if (Err)
1062 return Err;
1063 Err = GenIndex(CDCtx);
1064 if (Err)
1065 return Err;
1066
1067 for (const auto &FilePath : CDCtx.UserStylesheets) {
1068 Err = CopyFile(FilePath, OutDirectory: CDCtx.OutDirectory);
1069 if (Err)
1070 return Err;
1071 }
1072 for (const auto &FilePath : CDCtx.FilesToCopy) {
1073 Err = CopyFile(FilePath, OutDirectory: CDCtx.OutDirectory);
1074 if (Err)
1075 return Err;
1076 }
1077 return llvm::Error::success();
1078}
1079
1080static GeneratorRegistry::Add<HTMLGenerator> HTML(HTMLGenerator::Format,
1081 "Generator for HTML output.");
1082
1083// This anchor is used to force the linker to link in the generated object
1084// file and thus register the generator.
1085volatile int HTMLGeneratorAnchorSource = 0;
1086
1087} // namespace doc
1088} // namespace clang
1089

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