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 | |
22 | using namespace llvm; |
23 | |
24 | namespace clang { |
25 | namespace doc { |
26 | |
27 | namespace { |
28 | |
29 | class HTMLTag { |
30 | public: |
31 | // Any other tag can be added if required |
32 | enum TagType { |
33 | TAG_A, |
34 | TAG_DIV, |
35 | , |
36 | TAG_H1, |
37 | TAG_H2, |
38 | TAG_H3, |
39 | , |
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 | |
61 | private: |
62 | TagType Value; |
63 | }; |
64 | |
65 | enum NodeType { |
66 | NODE_TEXT, |
67 | NODE_TAG, |
68 | }; |
69 | |
70 | struct 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 | |
78 | struct 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 | |
86 | struct 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 | |
100 | constexpr const char *kDoctypeDecl = "<!DOCTYPE html>" ; |
101 | |
102 | struct 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 | |
115 | bool 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 | |
140 | llvm::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 | |
180 | void TextNode::Render(llvm::raw_ostream &OS, int IndentationLevel) { |
181 | OS.indent(NumSpaces: IndentationLevel * 2); |
182 | printHTMLEscaped(String: Text, Out&: OS); |
183 | } |
184 | |
185 | void 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 | |
222 | template <typename Derived, typename Base, |
223 | typename = std::enable_if<std::is_base_of<Derived, Base>::value>> |
224 | static 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 |
230 | static 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 | |
272 | static std::vector<std::unique_ptr<TagNode>> |
273 | genStylesheetsHTML(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 | |
289 | static std::vector<std::unique_ptr<TagNode>> |
290 | genJsScriptsHTML(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 | |
304 | static 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 | |
310 | static std::unique_ptr<HTMLNode> |
311 | genReference(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 | |
329 | static std::vector<std::unique_ptr<HTMLNode>> |
330 | genReferenceList(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 | |
341 | static std::vector<std::unique_ptr<TagNode>> |
342 | genHTML(const EnumInfo &I, const ClangDocContext &CDCtx); |
343 | static std::vector<std::unique_ptr<TagNode>> |
344 | genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx, |
345 | StringRef ParentInfoDir); |
346 | |
347 | static std::vector<std::unique_ptr<TagNode>> |
348 | genEnumsBlock(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 | |
365 | static std::unique_ptr<TagNode> |
366 | genEnumMembersBlock(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 | |
377 | static std::vector<std::unique_ptr<TagNode>> |
378 | genFunctionsBlock(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 | |
396 | static std::vector<std::unique_ptr<TagNode>> |
397 | genRecordMembersBlock(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 | |
420 | static std::vector<std::unique_ptr<TagNode>> |
421 | genReferencesBlock(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 | |
439 | static std::unique_ptr<TagNode> |
440 | writeFileDefinition(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 | |
465 | static std::vector<std::unique_ptr<TagNode>> |
466 | genHTML(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 |
471 | static std::vector<std::unique_ptr<TagNode>> |
472 | genFileHeadNodes(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 |
490 | static std::unique_ptr<TagNode> (StringRef ProjectName) { |
491 | auto = 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) |
500 | static 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 = 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 = 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 |
535 | static std::unique_ptr<TagNode> () { |
536 | auto = 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 |
545 | static HTMLFile |
546 | genInfoFile(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> = genFileHeaderNode(ProjectName: CDCtx.ProjectName); |
554 | std::unique_ptr<TagNode> MainNode = |
555 | genInfoFileMainNode(InfoPath, MainContentInnerNodes&: MainContentNodes, InfoIndex); |
556 | std::unique_ptr<TagNode> = 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 | |
566 | template <typename T, |
567 | typename = std::enable_if<std::is_base_of<T, Info>::value>> |
568 | static 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 | |
576 | static std::vector<std::unique_ptr<TagNode>> |
577 | genHTML(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 | |
603 | static std::unique_ptr<HTMLNode> (const CommentInfo &I) { |
604 | if (I.Kind == "FullComment" ) { |
605 | auto = 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 = 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 | |
630 | static std::unique_ptr<TagNode> (const std::vector<CommentInfo> &C) { |
631 | auto = 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 | |
639 | static std::vector<std::unique_ptr<TagNode>> |
640 | genHTML(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 | |
672 | static std::vector<std::unique_ptr<TagNode>> |
673 | genHTML(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 & = 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 | |
721 | static std::vector<std::unique_ptr<TagNode>> |
722 | genHTML(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 | |
766 | static std::vector<std::unique_ptr<TagNode>> |
767 | genHTML(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 | |
832 | static std::vector<std::unique_ptr<TagNode>> |
833 | genHTML(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. |
840 | class HTMLGenerator : public Generator { |
841 | public: |
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 | |
852 | const char *HTMLGenerator::Format = "html" ; |
853 | |
854 | llvm::Error |
855 | HTMLGenerator::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 | |
907 | llvm::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 | |
944 | static 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 | |
962 | static 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 |
997 | static std::unique_ptr<TagNode> genIndexFileMainNode() { |
998 | auto MainNode = std::make_unique<TagNode>(args: HTMLTag::TAG_MAIN); |
999 | |
1000 | auto = 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 | |
1012 | static 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> = genFileHeaderNode(ProjectName: CDCtx.ProjectName); |
1029 | std::unique_ptr<TagNode> MainNode = genIndexFileMainNode(); |
1030 | std::unique_ptr<TagNode> = 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 | |
1042 | static 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 | |
1059 | llvm::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 | |
1080 | static 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. |
1085 | volatile int HTMLGeneratorAnchorSource = 0; |
1086 | |
1087 | } // namespace doc |
1088 | } // namespace clang |
1089 | |