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} 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; 86 for (const auto &Child : I.Children) 87 writeDescription(*Child, OS); 88 } else if (I.Kind == "TParamCommandComment") { 89 std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; 90 OS << genEmphasis(I.ParamName) << I.Text << Direction; 91 for (const auto &Child : I.Children) 92 writeDescription(*Child, OS); 93 } else if (I.Kind == "VerbatimBlockComment") { 94 for (const auto &Child : I.Children) 95 writeDescription(*Child, OS); 96 } else if (I.Kind == "VerbatimBlockLineComment") { 97 OS << I.Text; 98 writeNewLine(OS); 99 } else if (I.Kind == "VerbatimLineComment") { 100 OS << I.Text; 101 writeNewLine(OS); 102 } else if (I.Kind == "HTMLStartTagComment") { 103 if (I.AttrKeys.size() != I.AttrValues.size()) 104 return; 105 std::string Buffer; 106 llvm::raw_string_ostream Attrs(Buffer); 107 for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx) 108 Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\""; 109 110 std::string CloseTag = I.SelfClosing ? "/>" : ">"; 111 writeLine("<" + I.Name + Attrs.str() + CloseTag, OS); 112 } else if (I.Kind == "HTMLEndTagComment") { 113 writeLine("</" + I.Name + ">", OS); 114 } else if (I.Kind == "TextComment") { 115 OS << I.Text; 116 } else { 117 OS << "Unknown comment kind: " << I.Kind << ".\n\n"; 118 } 119 } 120 121 static void writeNameLink(const StringRef &CurrentPath, const Reference &R, 122 llvm::raw_ostream &OS) { 123 llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath); 124 // Paths in Markdown use POSIX separators. 125 llvm::sys::path::native(Path, llvm::sys::path::Style::posix); 126 llvm::sys::path::append(Path, llvm::sys::path::Style::posix, 127 R.getFileBaseName() + ".md"); 128 OS << "[" << R.Name << "](" << Path << ")"; 129 } 130 131 static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I, 132 llvm::raw_ostream &OS) { 133 if (I.Scoped) 134 writeLine("| enum class " + I.Name + " |", OS); 135 else 136 writeLine("| enum " + I.Name + " |", OS); 137 writeLine("--", OS); 138 139 std::string Buffer; 140 llvm::raw_string_ostream Members(Buffer); 141 if (!I.Members.empty()) 142 for (const auto &N : I.Members) 143 Members << "| " << N.Name << " |\n"; 144 writeLine(Members.str(), OS); 145 if (I.DefLoc) 146 writeFileDefinition(CDCtx, *I.DefLoc, OS); 147 148 for (const auto &C : I.Description) 149 writeDescription(C, OS); 150 } 151 152 static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I, 153 llvm::raw_ostream &OS) { 154 std::string Buffer; 155 llvm::raw_string_ostream Stream(Buffer); 156 bool First = true; 157 for (const auto &N : I.Params) { 158 if (!First) 159 Stream << ", "; 160 Stream << N.Type.QualName + " " + N.Name; 161 First = false; 162 } 163 writeHeader(I.Name, 3, OS); 164 std::string Access = getAccessSpelling(I.Access).str(); 165 if (Access != "") 166 writeLine(genItalic(Access + " " + I.ReturnType.Type.QualName + " " + 167 I.Name + "(" + Stream.str() + ")"), 168 OS); 169 else 170 writeLine(genItalic(I.ReturnType.Type.QualName + " " + I.Name + "(" + 171 Stream.str() + ")"), 172 OS); 173 if (I.DefLoc) 174 writeFileDefinition(CDCtx, *I.DefLoc, OS); 175 176 for (const auto &C : I.Description) 177 writeDescription(C, OS); 178 } 179 180 static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I, 181 llvm::raw_ostream &OS) { 182 if (I.Name == "") 183 writeHeader("Global Namespace", 1, OS); 184 else 185 writeHeader("namespace " + I.Name, 1, OS); 186 writeNewLine(OS); 187 188 if (!I.Description.empty()) { 189 for (const auto &C : I.Description) 190 writeDescription(C, OS); 191 writeNewLine(OS); 192 } 193 194 llvm::SmallString<64> BasePath = I.getRelativeFilePath(""); 195 196 if (!I.Children.Namespaces.empty()) { 197 writeHeader("Namespaces", 2, OS); 198 for (const auto &R : I.Children.Namespaces) { 199 OS << "* "; 200 writeNameLink(BasePath, R, OS); 201 OS << "\n"; 202 } 203 writeNewLine(OS); 204 } 205 206 if (!I.Children.Records.empty()) { 207 writeHeader("Records", 2, OS); 208 for (const auto &R : I.Children.Records) { 209 OS << "* "; 210 writeNameLink(BasePath, R, OS); 211 OS << "\n"; 212 } 213 writeNewLine(OS); 214 } 215 216 if (!I.Children.Functions.empty()) { 217 writeHeader("Functions", 2, OS); 218 for (const auto &F : I.Children.Functions) 219 genMarkdown(CDCtx, F, OS); 220 writeNewLine(OS); 221 } 222 if (!I.Children.Enums.empty()) { 223 writeHeader("Enums", 2, OS); 224 for (const auto &E : I.Children.Enums) 225 genMarkdown(CDCtx, E, OS); 226 writeNewLine(OS); 227 } 228 } 229 230 static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I, 231 llvm::raw_ostream &OS) { 232 writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS); 233 if (I.DefLoc) 234 writeFileDefinition(CDCtx, *I.DefLoc, OS); 235 236 if (!I.Description.empty()) { 237 for (const auto &C : I.Description) 238 writeDescription(C, OS); 239 writeNewLine(OS); 240 } 241 242 std::string Parents = genReferenceList(I.Parents); 243 std::string VParents = genReferenceList(I.VirtualParents); 244 if (!Parents.empty() || !VParents.empty()) { 245 if (Parents.empty()) 246 writeLine("Inherits from " + VParents, OS); 247 else if (VParents.empty()) 248 writeLine("Inherits from " + Parents, OS); 249 else 250 writeLine("Inherits from " + Parents + ", " + VParents, OS); 251 writeNewLine(OS); 252 } 253 254 if (!I.Members.empty()) { 255 writeHeader("Members", 2, OS); 256 for (const auto &Member : I.Members) { 257 std::string Access = getAccessSpelling(Member.Access).str(); 258 if (Access != "") 259 writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS); 260 else 261 writeLine(Member.Type.Name + " " + Member.Name, OS); 262 } 263 writeNewLine(OS); 264 } 265 266 if (!I.Children.Records.empty()) { 267 writeHeader("Records", 2, OS); 268 for (const auto &R : I.Children.Records) 269 writeLine(R.Name, OS); 270 writeNewLine(OS); 271 } 272 if (!I.Children.Functions.empty()) { 273 writeHeader("Functions", 2, OS); 274 for (const auto &F : I.Children.Functions) 275 genMarkdown(CDCtx, F, OS); 276 writeNewLine(OS); 277 } 278 if (!I.Children.Enums.empty()) { 279 writeHeader("Enums", 2, OS); 280 for (const auto &E : I.Children.Enums) 281 genMarkdown(CDCtx, E, OS); 282 writeNewLine(OS); 283 } 284 } 285 286 static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I, 287 llvm::raw_ostream &OS) { 288 // TODO support typedefs in markdown. 289 } 290 291 static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) { 292 // Write out the heading level starting at ## 293 OS << "##" << std::string(Level, '#') << " "; 294 writeNameLink("", I, OS); 295 OS << "\n"; 296 } 297 298 static llvm::Error serializeIndex(ClangDocContext &CDCtx) { 299 std::error_code FileErr; 300 llvm::SmallString<128> FilePath; 301 llvm::sys::path::native(CDCtx.OutDirectory, FilePath); 302 llvm::sys::path::append(FilePath, "all_files.md"); 303 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None); 304 if (FileErr) 305 return llvm::createStringError(llvm::inconvertibleErrorCode(), 306 "error creating index file: " + 307 FileErr.message()); 308 309 CDCtx.Idx.sort(); 310 OS << "# All Files"; 311 if (!CDCtx.ProjectName.empty()) 312 OS << " for " << CDCtx.ProjectName; 313 OS << "\n\n"; 314 315 for (auto C : CDCtx.Idx.Children) 316 serializeReference(OS, C, 0); 317 318 return llvm::Error::success(); 319 } 320 321 static llvm::Error genIndex(ClangDocContext &CDCtx) { 322 std::error_code FileErr; 323 llvm::SmallString<128> FilePath; 324 llvm::sys::path::native(CDCtx.OutDirectory, FilePath); 325 llvm::sys::path::append(FilePath, "index.md"); 326 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None); 327 if (FileErr) 328 return llvm::createStringError(llvm::inconvertibleErrorCode(), 329 "error creating index file: " + 330 FileErr.message()); 331 CDCtx.Idx.sort(); 332 OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n"; 333 for (auto C : CDCtx.Idx.Children) { 334 if (!C.Children.empty()) { 335 const char *Type; 336 switch (C.RefType) { 337 case InfoType::IT_namespace: 338 Type = "Namespace"; 339 break; 340 case InfoType::IT_record: 341 Type = "Type"; 342 break; 343 case InfoType::IT_enum: 344 Type = "Enum"; 345 break; 346 case InfoType::IT_function: 347 Type = "Function"; 348 break; 349 case InfoType::IT_typedef: 350 Type = "Typedef"; 351 break; 352 case InfoType::IT_default: 353 Type = "Other"; 354 } 355 OS << "* " << Type << ": [" << C.Name << "]("; 356 if (!C.Path.empty()) 357 OS << C.Path << "/"; 358 OS << C.Name << ")\n"; 359 } 360 } 361 return llvm::Error::success(); 362 } 363 364 /// Generator for Markdown documentation. 365 class MDGenerator : public Generator { 366 public: 367 static const char *Format; 368 369 llvm::Error generateDocs(StringRef RootDir, 370 llvm::StringMap<std::unique_ptr<doc::Info>> Infos, 371 const ClangDocContext &CDCtx) override; 372 llvm::Error createResources(ClangDocContext &CDCtx) override; 373 llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, 374 const ClangDocContext &CDCtx) override; 375 }; 376 377 const char *MDGenerator::Format = "md"; 378 379 llvm::Error 380 MDGenerator::generateDocs(StringRef RootDir, 381 llvm::StringMap<std::unique_ptr<doc::Info>> Infos, 382 const ClangDocContext &CDCtx) { 383 // Track which directories we already tried to create. 384 llvm::StringSet<> CreatedDirs; 385 386 // Collect all output by file name and create the necessary directories. 387 llvm::StringMap<std::vector<doc::Info *>> FileToInfos; 388 for (const auto &Group : Infos) { 389 doc::Info *Info = Group.getValue().get(); 390 391 llvm::SmallString<128> Path; 392 llvm::sys::path::native(RootDir, Path); 393 llvm::sys::path::append(Path, Info->getRelativeFilePath("")); 394 if (!CreatedDirs.contains(Path)) { 395 if (std::error_code Err = llvm::sys::fs::create_directories(Path); 396 Err != std::error_code()) { 397 return llvm::createStringError(Err, "Failed to create directory '%s'.", 398 Path.c_str()); 399 } 400 CreatedDirs.insert(Path); 401 } 402 403 llvm::sys::path::append(Path, Info->getFileBaseName() + ".md"); 404 FileToInfos[Path].push_back(Info); 405 } 406 407 for (const auto &Group : FileToInfos) { 408 std::error_code FileErr; 409 llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, 410 llvm::sys::fs::OF_None); 411 if (FileErr) { 412 return llvm::createStringError(FileErr, "Error opening file '%s'", 413 Group.getKey().str().c_str()); 414 } 415 416 for (const auto &Info : Group.getValue()) { 417 if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) { 418 return Err; 419 } 420 } 421 } 422 423 return llvm::Error::success(); 424 } 425 426 llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, 427 const ClangDocContext &CDCtx) { 428 switch (I->IT) { 429 case InfoType::IT_namespace: 430 genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS); 431 break; 432 case InfoType::IT_record: 433 genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS); 434 break; 435 case InfoType::IT_enum: 436 genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS); 437 break; 438 case InfoType::IT_function: 439 genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS); 440 break; 441 case InfoType::IT_typedef: 442 genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS); 443 break; 444 case InfoType::IT_default: 445 return createStringError(llvm::inconvertibleErrorCode(), 446 "unexpected InfoType"); 447 } 448 return llvm::Error::success(); 449 } 450 451 llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) { 452 // Write an all_files.md 453 auto Err = serializeIndex(CDCtx); 454 if (Err) 455 return Err; 456 457 // Generate the index page. 458 Err = genIndex(CDCtx); 459 if (Err) 460 return Err; 461 462 return llvm::Error::success(); 463 } 464 465 static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format, 466 "Generator for MD output."); 467 468 // This anchor is used to force the linker to link in the generated object 469 // file and thus register the generator. 470 volatile int MDGeneratorAnchorSource = 0; 471 472 } // namespace doc 473 } // namespace clang 474