1 //===-- MDGenerator.cpp - Markdown 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 "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 // Markdown generation 22 23 static std::string genItalic(const Twine &Text) { 24 return "*" + Text.str() + "*"; 25 } 26 27 static std::string genEmphasis(const Twine &Text) { 28 return "**" + Text.str() + "**"; 29 } 30 31 static std::string 32 genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) { 33 std::string Buffer; 34 llvm::raw_string_ostream Stream(Buffer); 35 for (const auto &R : Refs) { 36 if (&R != Refs.begin()) 37 Stream << ", "; 38 Stream << R.Name; 39 } 40 return Stream.str(); 41 } 42 43 static void writeLine(const Twine &Text, raw_ostream &OS) { 44 OS << Text << "\n\n"; 45 } 46 47 static void writeNewLine(raw_ostream &OS) { OS << "\n\n"; } 48 49 static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) { 50 OS << std::string(Num, '#') + " " + Text << "\n\n"; 51 } 52 53 static void writeFileDefinition(const ClangDocContext &CDCtx, const Location &L, 54 raw_ostream &OS) { 55 56 if (!CDCtx.RepositoryUrl) { 57 OS << "*Defined at " << L.Filename << "#" << std::to_string(L.LineNumber) 58 << "*"; 59 } else { 60 OS << "*Defined at [" << L.Filename << "#" << std::to_string(L.LineNumber) 61 << "](" << StringRef{CDCtx.RepositoryUrl.getValue()} 62 << llvm::sys::path::relative_path(L.Filename) << "#" 63 << std::to_string(L.LineNumber) << ")" 64 << "*"; 65 } 66 OS << "\n\n"; 67 } 68 69 static void writeDescription(const CommentInfo &I, raw_ostream &OS) { 70 if (I.Kind == "FullComment") { 71 for (const auto &Child : I.Children) 72 writeDescription(*Child, OS); 73 } else if (I.Kind == "ParagraphComment") { 74 for (const auto &Child : I.Children) 75 writeDescription(*Child, OS); 76 writeNewLine(OS); 77 } else if (I.Kind == "BlockCommandComment") { 78 OS << genEmphasis(I.Name); 79 for (const auto &Child : I.Children) 80 writeDescription(*Child, OS); 81 } else if (I.Kind == "InlineCommandComment") { 82 OS << genEmphasis(I.Name) << " " << I.Text; 83 } else if (I.Kind == "ParamCommandComment") { 84 std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; 85 OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n"; 86 } else if (I.Kind == "TParamCommandComment") { 87 std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; 88 OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n"; 89 } else if (I.Kind == "VerbatimBlockComment") { 90 for (const auto &Child : I.Children) 91 writeDescription(*Child, OS); 92 } else if (I.Kind == "VerbatimBlockLineComment") { 93 OS << I.Text; 94 writeNewLine(OS); 95 } else if (I.Kind == "VerbatimLineComment") { 96 OS << I.Text; 97 writeNewLine(OS); 98 } else if (I.Kind == "HTMLStartTagComment") { 99 if (I.AttrKeys.size() != I.AttrValues.size()) 100 return; 101 std::string Buffer; 102 llvm::raw_string_ostream Attrs(Buffer); 103 for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx) 104 Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\""; 105 106 std::string CloseTag = I.SelfClosing ? "/>" : ">"; 107 writeLine("<" + I.Name + Attrs.str() + CloseTag, OS); 108 } else if (I.Kind == "HTMLEndTagComment") { 109 writeLine("</" + I.Name + ">", OS); 110 } else if (I.Kind == "TextComment") { 111 OS << I.Text; 112 } else { 113 OS << "Unknown comment kind: " << I.Kind << ".\n\n"; 114 } 115 } 116 117 static void writeNameLink(const StringRef &CurrentPath, const Reference &R, 118 llvm::raw_ostream &OS) { 119 llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath); 120 // Paths in Markdown use POSIX separators. 121 llvm::sys::path::native(Path, llvm::sys::path::Style::posix); 122 llvm::sys::path::append(Path, llvm::sys::path::Style::posix, 123 R.getFileBaseName() + ".md"); 124 OS << "[" << R.Name << "](" << Path << ")"; 125 } 126 127 static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I, 128 llvm::raw_ostream &OS) { 129 if (I.Scoped) 130 writeLine("| enum class " + I.Name + " |", OS); 131 else 132 writeLine("| enum " + I.Name + " |", OS); 133 writeLine("--", OS); 134 135 std::string Buffer; 136 llvm::raw_string_ostream Members(Buffer); 137 if (!I.Members.empty()) 138 for (const auto &N : I.Members) 139 Members << "| " << N << " |\n"; 140 writeLine(Members.str(), OS); 141 if (I.DefLoc) 142 writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS); 143 144 for (const auto &C : I.Description) 145 writeDescription(C, OS); 146 } 147 148 static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I, 149 llvm::raw_ostream &OS) { 150 std::string Buffer; 151 llvm::raw_string_ostream Stream(Buffer); 152 bool First = true; 153 for (const auto &N : I.Params) { 154 if (!First) 155 Stream << ", "; 156 Stream << N.Type.Name + " " + N.Name; 157 First = false; 158 } 159 writeHeader(I.Name, 3, OS); 160 std::string Access = getAccessSpelling(I.Access).str(); 161 if (Access != "") 162 writeLine(genItalic(Access + " " + I.ReturnType.Type.Name + " " + I.Name + 163 "(" + Stream.str() + ")"), 164 OS); 165 else 166 writeLine(genItalic(I.ReturnType.Type.Name + " " + I.Name + "(" + 167 Stream.str() + ")"), 168 OS); 169 if (I.DefLoc) 170 writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS); 171 172 for (const auto &C : I.Description) 173 writeDescription(C, OS); 174 } 175 176 static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I, 177 llvm::raw_ostream &OS) { 178 if (I.Name == "") 179 writeHeader("Global Namespace", 1, OS); 180 else 181 writeHeader("namespace " + I.Name, 1, OS); 182 writeNewLine(OS); 183 184 if (!I.Description.empty()) { 185 for (const auto &C : I.Description) 186 writeDescription(C, OS); 187 writeNewLine(OS); 188 } 189 190 llvm::SmallString<64> BasePath = I.getRelativeFilePath(""); 191 192 if (!I.ChildNamespaces.empty()) { 193 writeHeader("Namespaces", 2, OS); 194 for (const auto &R : I.ChildNamespaces) { 195 OS << "* "; 196 writeNameLink(BasePath, R, OS); 197 OS << "\n"; 198 } 199 writeNewLine(OS); 200 } 201 202 if (!I.ChildRecords.empty()) { 203 writeHeader("Records", 2, OS); 204 for (const auto &R : I.ChildRecords) { 205 OS << "* "; 206 writeNameLink(BasePath, R, OS); 207 OS << "\n"; 208 } 209 writeNewLine(OS); 210 } 211 212 if (!I.ChildFunctions.empty()) { 213 writeHeader("Functions", 2, OS); 214 for (const auto &F : I.ChildFunctions) 215 genMarkdown(CDCtx, F, OS); 216 writeNewLine(OS); 217 } 218 if (!I.ChildEnums.empty()) { 219 writeHeader("Enums", 2, OS); 220 for (const auto &E : I.ChildEnums) 221 genMarkdown(CDCtx, E, OS); 222 writeNewLine(OS); 223 } 224 } 225 226 static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I, 227 llvm::raw_ostream &OS) { 228 writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS); 229 if (I.DefLoc) 230 writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS); 231 232 if (!I.Description.empty()) { 233 for (const auto &C : I.Description) 234 writeDescription(C, OS); 235 writeNewLine(OS); 236 } 237 238 std::string Parents = genReferenceList(I.Parents); 239 std::string VParents = genReferenceList(I.VirtualParents); 240 if (!Parents.empty() || !VParents.empty()) { 241 if (Parents.empty()) 242 writeLine("Inherits from " + VParents, OS); 243 else if (VParents.empty()) 244 writeLine("Inherits from " + Parents, OS); 245 else 246 writeLine("Inherits from " + Parents + ", " + VParents, OS); 247 writeNewLine(OS); 248 } 249 250 if (!I.Members.empty()) { 251 writeHeader("Members", 2, OS); 252 for (const auto &Member : I.Members) { 253 std::string Access = getAccessSpelling(Member.Access).str(); 254 if (Access != "") 255 writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS); 256 else 257 writeLine(Member.Type.Name + " " + Member.Name, OS); 258 } 259 writeNewLine(OS); 260 } 261 262 if (!I.ChildRecords.empty()) { 263 writeHeader("Records", 2, OS); 264 for (const auto &R : I.ChildRecords) 265 writeLine(R.Name, OS); 266 writeNewLine(OS); 267 } 268 if (!I.ChildFunctions.empty()) { 269 writeHeader("Functions", 2, OS); 270 for (const auto &F : I.ChildFunctions) 271 genMarkdown(CDCtx, F, OS); 272 writeNewLine(OS); 273 } 274 if (!I.ChildEnums.empty()) { 275 writeHeader("Enums", 2, OS); 276 for (const auto &E : I.ChildEnums) 277 genMarkdown(CDCtx, E, OS); 278 writeNewLine(OS); 279 } 280 } 281 282 static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) { 283 // Write out the heading level starting at ## 284 OS << "##" << std::string(Level, '#') << " "; 285 writeNameLink("", I, OS); 286 OS << "\n"; 287 } 288 289 static llvm::Error serializeIndex(ClangDocContext &CDCtx) { 290 std::error_code FileErr; 291 llvm::SmallString<128> FilePath; 292 llvm::sys::path::native(CDCtx.OutDirectory, FilePath); 293 llvm::sys::path::append(FilePath, "all_files.md"); 294 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None); 295 if (FileErr) 296 return llvm::createStringError(llvm::inconvertibleErrorCode(), 297 "error creating index file: " + 298 FileErr.message()); 299 300 CDCtx.Idx.sort(); 301 OS << "# All Files"; 302 if (!CDCtx.ProjectName.empty()) 303 OS << " for " << CDCtx.ProjectName; 304 OS << "\n\n"; 305 306 for (auto C : CDCtx.Idx.Children) 307 serializeReference(OS, C, 0); 308 309 return llvm::Error::success(); 310 } 311 312 static llvm::Error genIndex(ClangDocContext &CDCtx) { 313 std::error_code FileErr; 314 llvm::SmallString<128> FilePath; 315 llvm::sys::path::native(CDCtx.OutDirectory, FilePath); 316 llvm::sys::path::append(FilePath, "index.md"); 317 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None); 318 if (FileErr) 319 return llvm::createStringError(llvm::inconvertibleErrorCode(), 320 "error creating index file: " + 321 FileErr.message()); 322 CDCtx.Idx.sort(); 323 OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n"; 324 for (auto C : CDCtx.Idx.Children) { 325 if (!C.Children.empty()) { 326 const char *Type; 327 switch (C.RefType) { 328 case InfoType::IT_namespace: 329 Type = "Namespace"; 330 break; 331 case InfoType::IT_record: 332 Type = "Type"; 333 break; 334 case InfoType::IT_enum: 335 Type = "Enum"; 336 break; 337 case InfoType::IT_function: 338 Type = "Function"; 339 break; 340 case InfoType::IT_default: 341 Type = "Other"; 342 } 343 OS << "* " << Type << ": [" << C.Name << "]("; 344 if (!C.Path.empty()) 345 OS << C.Path << "/"; 346 OS << C.Name << ")\n"; 347 } 348 } 349 return llvm::Error::success(); 350 } 351 /// Generator for Markdown documentation. 352 class MDGenerator : public Generator { 353 public: 354 static const char *Format; 355 356 llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, 357 const ClangDocContext &CDCtx) override; 358 llvm::Error createResources(ClangDocContext &CDCtx) override; 359 }; 360 361 const char *MDGenerator::Format = "md"; 362 363 llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, 364 const ClangDocContext &CDCtx) { 365 switch (I->IT) { 366 case InfoType::IT_namespace: 367 genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS); 368 break; 369 case InfoType::IT_record: 370 genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS); 371 break; 372 case InfoType::IT_enum: 373 genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS); 374 break; 375 case InfoType::IT_function: 376 genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS); 377 break; 378 case InfoType::IT_default: 379 return createStringError(llvm::inconvertibleErrorCode(), 380 "unexpected InfoType"); 381 } 382 return llvm::Error::success(); 383 } 384 385 llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) { 386 // Write an all_files.md 387 auto Err = serializeIndex(CDCtx); 388 if (Err) 389 return Err; 390 391 // Generate the index page. 392 Err = genIndex(CDCtx); 393 if (Err) 394 return Err; 395 396 return llvm::Error::success(); 397 } 398 399 static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format, 400 "Generator for MD output."); 401 402 // This anchor is used to force the linker to link in the generated object 403 // file and thus register the generator. 404 volatile int MDGeneratorAnchorSource = 0; 405 406 } // namespace doc 407 } // namespace clang 408