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