Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 1 | //===-- MDGenerator.cpp - Markdown Generator --------------------*- C++ -*-===// |
| 2 | // |
Chandler Carruth | 2946cd7 | 2019-01-19 08:50:56 +0000 | [diff] [blame] | 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 |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "Generators.h" |
| 10 | #include "Representation.h" |
| 11 | #include "llvm/ADT/StringRef.h" |
| 12 | #include "llvm/Support/FileSystem.h" |
| 13 | #include "llvm/Support/Path.h" |
| 14 | #include <string> |
| 15 | |
| 16 | using namespace llvm; |
| 17 | |
| 18 | namespace clang { |
| 19 | namespace doc { |
| 20 | |
| 21 | // Enum conversion |
| 22 | |
| 23 | std::string getAccess(AccessSpecifier AS) { |
| 24 | switch (AS) { |
| 25 | case AccessSpecifier::AS_public: |
| 26 | return "public"; |
| 27 | case AccessSpecifier::AS_protected: |
| 28 | return "protected"; |
| 29 | case AccessSpecifier::AS_private: |
| 30 | return "private"; |
| 31 | case AccessSpecifier::AS_none: |
| 32 | return {}; |
| 33 | } |
Simon Pilgrim | a346796 | 2018-09-12 12:56:58 +0000 | [diff] [blame] | 34 | llvm_unreachable("Unknown AccessSpecifier"); |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 35 | } |
| 36 | |
| 37 | std::string getTagType(TagTypeKind AS) { |
| 38 | switch (AS) { |
| 39 | case TagTypeKind::TTK_Class: |
| 40 | return "class"; |
| 41 | case TagTypeKind::TTK_Union: |
| 42 | return "union"; |
| 43 | case TagTypeKind::TTK_Interface: |
| 44 | return "interface"; |
| 45 | case TagTypeKind::TTK_Struct: |
| 46 | return "struct"; |
| 47 | case TagTypeKind::TTK_Enum: |
| 48 | return "enum"; |
| 49 | } |
Simon Pilgrim | a346796 | 2018-09-12 12:56:58 +0000 | [diff] [blame] | 50 | llvm_unreachable("Unknown TagTypeKind"); |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 51 | } |
| 52 | |
| 53 | // Markdown generation |
| 54 | |
| 55 | std::string genItalic(const Twine &Text) { return "*" + Text.str() + "*"; } |
| 56 | |
| 57 | std::string genEmphasis(const Twine &Text) { return "**" + Text.str() + "**"; } |
| 58 | |
| 59 | std::string genLink(const Twine &Text, const Twine &Link) { |
| 60 | return "[" + Text.str() + "](" + Link.str() + ")"; |
| 61 | } |
| 62 | |
| 63 | std::string genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) { |
| 64 | std::string Buffer; |
| 65 | llvm::raw_string_ostream Stream(Buffer); |
| 66 | bool First = true; |
| 67 | for (const auto &R : Refs) { |
| 68 | if (!First) |
| 69 | Stream << ", "; |
| 70 | Stream << R.Name; |
| 71 | First = false; |
| 72 | } |
| 73 | return Stream.str(); |
| 74 | } |
| 75 | |
Julie Hockett | e122f94 | 2018-10-04 21:34:13 +0000 | [diff] [blame] | 76 | void writeLine(const Twine &Text, raw_ostream &OS) { OS << Text << "\n\n"; } |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 77 | |
Julie Hockett | e122f94 | 2018-10-04 21:34:13 +0000 | [diff] [blame] | 78 | void writeNewLine(raw_ostream &OS) { OS << "\n\n"; } |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 79 | |
| 80 | void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) { |
Julie Hockett | e122f94 | 2018-10-04 21:34:13 +0000 | [diff] [blame] | 81 | OS << std::string(Num, '#') + " " + Text << "\n\n"; |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 82 | } |
| 83 | |
| 84 | void writeFileDefinition(const Location &L, raw_ostream &OS) { |
| 85 | OS << genItalic("Defined at line " + std::to_string(L.LineNumber) + " of " + |
| 86 | L.Filename) |
Julie Hockett | e122f94 | 2018-10-04 21:34:13 +0000 | [diff] [blame] | 87 | << "\n\n"; |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 88 | } |
| 89 | |
| 90 | void writeDescription(const CommentInfo &I, raw_ostream &OS) { |
| 91 | if (I.Kind == "FullComment") { |
| 92 | for (const auto &Child : I.Children) |
| 93 | writeDescription(*Child, OS); |
| 94 | } else if (I.Kind == "ParagraphComment") { |
| 95 | for (const auto &Child : I.Children) |
| 96 | writeDescription(*Child, OS); |
| 97 | writeNewLine(OS); |
| 98 | } else if (I.Kind == "BlockCommandComment") { |
| 99 | OS << genEmphasis(I.Name); |
| 100 | for (const auto &Child : I.Children) |
| 101 | writeDescription(*Child, OS); |
| 102 | } else if (I.Kind == "InlineCommandComment") { |
| 103 | OS << genEmphasis(I.Name) << " " << I.Text; |
| 104 | } else if (I.Kind == "ParamCommandComment") { |
| 105 | std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; |
Julie Hockett | e122f94 | 2018-10-04 21:34:13 +0000 | [diff] [blame] | 106 | OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n"; |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 107 | } else if (I.Kind == "TParamCommandComment") { |
| 108 | std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; |
Julie Hockett | e122f94 | 2018-10-04 21:34:13 +0000 | [diff] [blame] | 109 | OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n"; |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 110 | } else if (I.Kind == "VerbatimBlockComment") { |
| 111 | for (const auto &Child : I.Children) |
| 112 | writeDescription(*Child, OS); |
| 113 | } else if (I.Kind == "VerbatimBlockLineComment") { |
| 114 | OS << I.Text; |
| 115 | writeNewLine(OS); |
| 116 | } else if (I.Kind == "VerbatimLineComment") { |
| 117 | OS << I.Text; |
| 118 | writeNewLine(OS); |
| 119 | } else if (I.Kind == "HTMLStartTagComment") { |
| 120 | if (I.AttrKeys.size() != I.AttrValues.size()) |
| 121 | return; |
| 122 | std::string Buffer; |
| 123 | llvm::raw_string_ostream Attrs(Buffer); |
| 124 | for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx) |
| 125 | Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\""; |
| 126 | |
| 127 | std::string CloseTag = I.SelfClosing ? "/>" : ">"; |
| 128 | writeLine("<" + I.Name + Attrs.str() + CloseTag, OS); |
| 129 | } else if (I.Kind == "HTMLEndTagComment") { |
| 130 | writeLine("</" + I.Name + ">", OS); |
| 131 | } else if (I.Kind == "TextComment") { |
| 132 | OS << I.Text; |
| 133 | } else { |
Julie Hockett | e122f94 | 2018-10-04 21:34:13 +0000 | [diff] [blame] | 134 | OS << "Unknown comment kind: " << I.Kind << ".\n\n"; |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 135 | } |
| 136 | } |
| 137 | |
| 138 | void genMarkdown(const EnumInfo &I, llvm::raw_ostream &OS) { |
| 139 | if (I.Scoped) |
| 140 | writeLine("| enum class " + I.Name + " |", OS); |
| 141 | else |
| 142 | writeLine("| enum " + I.Name + " |", OS); |
| 143 | writeLine("--", OS); |
| 144 | |
| 145 | std::string Buffer; |
| 146 | llvm::raw_string_ostream Members(Buffer); |
| 147 | if (!I.Members.empty()) |
| 148 | for (const auto &N : I.Members) |
| 149 | Members << "| " << N << " |\n"; |
| 150 | writeLine(Members.str(), OS); |
| 151 | if (I.DefLoc) |
| 152 | writeFileDefinition(I.DefLoc.getValue(), OS); |
| 153 | |
| 154 | for (const auto &C : I.Description) |
| 155 | writeDescription(C, OS); |
| 156 | } |
| 157 | |
| 158 | void genMarkdown(const FunctionInfo &I, llvm::raw_ostream &OS) { |
| 159 | std::string Buffer; |
| 160 | llvm::raw_string_ostream Stream(Buffer); |
| 161 | bool First = true; |
| 162 | for (const auto &N : I.Params) { |
| 163 | if (!First) |
| 164 | Stream << ", "; |
| 165 | Stream << N.Type.Name + " " + N.Name; |
| 166 | First = false; |
| 167 | } |
Julie Hockett | e122f94 | 2018-10-04 21:34:13 +0000 | [diff] [blame] | 168 | writeHeader(I.Name, 3, OS); |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 169 | std::string Access = getAccess(I.Access); |
| 170 | if (Access != "") |
Julie Hockett | e122f94 | 2018-10-04 21:34:13 +0000 | [diff] [blame] | 171 | writeLine(genItalic(Access + " " + I.ReturnType.Type.Name + " " + I.Name + |
| 172 | "(" + Stream.str() + ")"), |
| 173 | OS); |
| 174 | else |
| 175 | writeLine(genItalic(I.ReturnType.Type.Name + " " + I.Name + "(" + |
| 176 | Stream.str() + ")"), |
| 177 | OS); |
Julie Hockett | ac68cab | 2018-09-11 15:56:55 +0000 | [diff] [blame] | 178 | if (I.DefLoc) |
| 179 | writeFileDefinition(I.DefLoc.getValue(), OS); |
| 180 | |
| 181 | for (const auto &C : I.Description) |
| 182 | writeDescription(C, OS); |
| 183 | } |
| 184 | |
| 185 | void genMarkdown(const NamespaceInfo &I, llvm::raw_ostream &OS) { |
| 186 | if (I.Name == "") |
| 187 | writeHeader("Global Namespace", 1, OS); |
| 188 | else |
| 189 | writeHeader("namespace " + I.Name, 1, OS); |
| 190 | writeNewLine(OS); |
| 191 | |
| 192 | if (!I.Description.empty()) { |
| 193 | for (const auto &C : I.Description) |
| 194 | writeDescription(C, OS); |
| 195 | writeNewLine(OS); |
| 196 | } |
| 197 | |
| 198 | if (!I.ChildNamespaces.empty()) { |
| 199 | writeHeader("Namespaces", 2, OS); |
| 200 | for (const auto &R : I.ChildNamespaces) |
| 201 | writeLine(R.Name, OS); |
| 202 | writeNewLine(OS); |
| 203 | } |
| 204 | if (!I.ChildRecords.empty()) { |
| 205 | writeHeader("Records", 2, OS); |
| 206 | for (const auto &R : I.ChildRecords) |
| 207 | writeLine(R.Name, OS); |
| 208 | writeNewLine(OS); |
| 209 | } |
| 210 | if (!I.ChildFunctions.empty()) { |
| 211 | writeHeader("Functions", 2, OS); |
| 212 | for (const auto &F : I.ChildFunctions) |
| 213 | genMarkdown(F, OS); |
| 214 | writeNewLine(OS); |
| 215 | } |
| 216 | if (!I.ChildEnums.empty()) { |
| 217 | writeHeader("Enums", 2, OS); |
| 218 | for (const auto &E : I.ChildEnums) |
| 219 | genMarkdown(E, OS); |
| 220 | writeNewLine(OS); |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | void genMarkdown(const RecordInfo &I, llvm::raw_ostream &OS) { |
| 225 | writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS); |
| 226 | if (I.DefLoc) |
| 227 | writeFileDefinition(I.DefLoc.getValue(), OS); |
| 228 | |
| 229 | if (!I.Description.empty()) { |
| 230 | for (const auto &C : I.Description) |
| 231 | writeDescription(C, OS); |
| 232 | writeNewLine(OS); |
| 233 | } |
| 234 | |
| 235 | std::string Parents = genReferenceList(I.Parents); |
| 236 | std::string VParents = genReferenceList(I.VirtualParents); |
| 237 | if (!Parents.empty() || !VParents.empty()) { |
| 238 | if (Parents.empty()) |
| 239 | writeLine("Inherits from " + VParents, OS); |
| 240 | else if (VParents.empty()) |
| 241 | writeLine("Inherits from " + Parents, OS); |
| 242 | else |
| 243 | writeLine("Inherits from " + Parents + ", " + VParents, OS); |
| 244 | writeNewLine(OS); |
| 245 | } |
| 246 | |
| 247 | if (!I.Members.empty()) { |
| 248 | writeHeader("Members", 2, OS); |
| 249 | for (const auto Member : I.Members) { |
| 250 | std::string Access = getAccess(Member.Access); |
| 251 | if (Access != "") |
| 252 | writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS); |
| 253 | else |
| 254 | writeLine(Member.Type.Name + " " + Member.Name, OS); |
| 255 | } |
| 256 | writeNewLine(OS); |
| 257 | } |
| 258 | |
| 259 | if (!I.ChildRecords.empty()) { |
| 260 | writeHeader("Records", 2, OS); |
| 261 | for (const auto &R : I.ChildRecords) |
| 262 | writeLine(R.Name, OS); |
| 263 | writeNewLine(OS); |
| 264 | } |
| 265 | if (!I.ChildFunctions.empty()) { |
| 266 | writeHeader("Functions", 2, OS); |
| 267 | for (const auto &F : I.ChildFunctions) |
| 268 | genMarkdown(F, OS); |
| 269 | writeNewLine(OS); |
| 270 | } |
| 271 | if (!I.ChildEnums.empty()) { |
| 272 | writeHeader("Enums", 2, OS); |
| 273 | for (const auto &E : I.ChildEnums) |
| 274 | genMarkdown(E, OS); |
| 275 | writeNewLine(OS); |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | /// Generator for Markdown documentation. |
| 280 | class MDGenerator : public Generator { |
| 281 | public: |
| 282 | static const char *Format; |
| 283 | |
| 284 | llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS) override; |
| 285 | }; |
| 286 | |
| 287 | const char *MDGenerator::Format = "md"; |
| 288 | |
| 289 | llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS) { |
| 290 | switch (I->IT) { |
| 291 | case InfoType::IT_namespace: |
| 292 | genMarkdown(*static_cast<clang::doc::NamespaceInfo *>(I), OS); |
| 293 | break; |
| 294 | case InfoType::IT_record: |
| 295 | genMarkdown(*static_cast<clang::doc::RecordInfo *>(I), OS); |
| 296 | break; |
| 297 | case InfoType::IT_enum: |
| 298 | genMarkdown(*static_cast<clang::doc::EnumInfo *>(I), OS); |
| 299 | break; |
| 300 | case InfoType::IT_function: |
| 301 | genMarkdown(*static_cast<clang::doc::FunctionInfo *>(I), OS); |
| 302 | break; |
| 303 | case InfoType::IT_default: |
| 304 | return llvm::make_error<llvm::StringError>("Unexpected info type.\n", |
| 305 | llvm::inconvertibleErrorCode()); |
| 306 | } |
| 307 | return llvm::Error::success(); |
| 308 | } |
| 309 | |
| 310 | static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format, |
| 311 | "Generator for MD output."); |
| 312 | |
| 313 | // This anchor is used to force the linker to link in the generated object file |
| 314 | // and thus register the generator. |
| 315 | volatile int MDGeneratorAnchorSource = 0; |
| 316 | |
| 317 | } // namespace doc |
| 318 | } // namespace clang |