509 lines · cpp
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-exception6//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/FormatVariadic.h"14#include "llvm/Support/Path.h"15#include "llvm/Support/raw_ostream.h"16#include <string>17 18using namespace llvm;19 20namespace clang {21namespace doc {22 23// Markdown generation24 25static std::string genItalic(const Twine &Text) {26 return "*" + Text.str() + "*";27}28 29static std::string genEmphasis(const Twine &Text) {30 return "**" + Text.str() + "**";31}32 33static std::string34genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) {35 std::string Buffer;36 llvm::raw_string_ostream Stream(Buffer);37 for (const auto &R : Refs) {38 if (&R != Refs.begin())39 Stream << ", ";40 Stream << R.Name;41 }42 return Stream.str();43}44 45static void writeLine(const Twine &Text, raw_ostream &OS) {46 OS << Text << "\n\n";47}48 49static void writeNewLine(raw_ostream &OS) { OS << "\n\n"; }50 51static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {52 OS << std::string(Num, '#') + " " + Text << "\n\n";53}54 55static void writeSourceFileRef(const ClangDocContext &CDCtx, const Location &L,56 raw_ostream &OS) {57 58 if (!CDCtx.RepositoryUrl) {59 OS << "*Defined at " << L.Filename << "#"60 << std::to_string(L.StartLineNumber) << "*";61 } else {62 63 OS << formatv("*Defined at [#{0}{1}{2}](#{0}{1}{3})*",64 CDCtx.RepositoryLinePrefix.value_or(""), L.StartLineNumber,65 L.Filename, *CDCtx.RepositoryUrl);66 }67 OS << "\n\n";68}69 70static void maybeWriteSourceFileRef(llvm::raw_ostream &OS,71 const ClangDocContext &CDCtx,72 const std::optional<Location> &DefLoc) {73 if (DefLoc)74 writeSourceFileRef(CDCtx, *DefLoc, OS);75}76 77static void writeDescription(const CommentInfo &I, raw_ostream &OS) {78 switch (I.Kind) {79 case CommentKind::CK_FullComment:80 for (const auto &Child : I.Children)81 writeDescription(*Child, OS);82 break;83 84 case CommentKind::CK_ParagraphComment:85 for (const auto &Child : I.Children)86 writeDescription(*Child, OS);87 writeNewLine(OS);88 break;89 90 case CommentKind::CK_BlockCommandComment:91 OS << genEmphasis(I.Name);92 for (const auto &Child : I.Children)93 writeDescription(*Child, OS);94 break;95 96 case CommentKind::CK_InlineCommandComment:97 OS << genEmphasis(I.Name) << " " << I.Text;98 break;99 100 case CommentKind::CK_ParamCommandComment:101 case CommentKind::CK_TParamCommandComment: {102 std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";103 OS << genEmphasis(I.ParamName) << I.Text << Direction;104 for (const auto &Child : I.Children)105 writeDescription(*Child, OS);106 break;107 }108 109 case CommentKind::CK_VerbatimBlockComment:110 for (const auto &Child : I.Children)111 writeDescription(*Child, OS);112 break;113 114 case CommentKind::CK_VerbatimBlockLineComment:115 case CommentKind::CK_VerbatimLineComment:116 OS << I.Text;117 writeNewLine(OS);118 break;119 120 case CommentKind::CK_HTMLStartTagComment: {121 if (I.AttrKeys.size() != I.AttrValues.size())122 return;123 std::string Buffer;124 llvm::raw_string_ostream Attrs(Buffer);125 for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx)126 Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\"";127 128 std::string CloseTag = I.SelfClosing ? "/>" : ">";129 writeLine("<" + I.Name + Attrs.str() + CloseTag, OS);130 break;131 }132 133 case CommentKind::CK_HTMLEndTagComment:134 writeLine("</" + I.Name + ">", OS);135 break;136 137 case CommentKind::CK_TextComment:138 OS << I.Text;139 break;140 141 case CommentKind::CK_Unknown:142 OS << "Unknown comment kind: " << static_cast<int>(I.Kind) << ".\n\n";143 break;144 }145}146 147static void writeNameLink(const StringRef &CurrentPath, const Reference &R,148 llvm::raw_ostream &OS) {149 llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath);150 // Paths in Markdown use POSIX separators.151 llvm::sys::path::native(Path, llvm::sys::path::Style::posix);152 llvm::sys::path::append(Path, llvm::sys::path::Style::posix,153 R.getFileBaseName() + ".md");154 OS << "[" << R.Name << "](" << Path << ")";155}156 157static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I,158 llvm::raw_ostream &OS) {159 if (I.Scoped)160 writeLine("| enum class " + I.Name + " |", OS);161 else162 writeLine("| enum " + I.Name + " |", OS);163 writeLine("--", OS);164 165 std::string Buffer;166 llvm::raw_string_ostream Members(Buffer);167 if (!I.Members.empty())168 for (const auto &N : I.Members)169 Members << "| " << N.Name << " |\n";170 writeLine(Members.str(), OS);171 172 maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);173 174 for (const auto &C : I.Description)175 writeDescription(C, OS);176}177 178static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,179 llvm::raw_ostream &OS) {180 std::string Buffer;181 llvm::raw_string_ostream Stream(Buffer);182 bool First = true;183 for (const auto &N : I.Params) {184 if (!First)185 Stream << ", ";186 Stream << N.Type.QualName + " " + N.Name;187 First = false;188 }189 writeHeader(I.Name, 3, OS);190 StringRef Access = getAccessSpelling(I.Access);191 writeLine(genItalic(Twine(Access) + (!Access.empty() ? " " : "") +192 (I.IsStatic ? "static " : "") +193 I.ReturnType.Type.QualName.str() + " " + I.Name.str() +194 "(" + Twine(Stream.str()) + ")"),195 OS);196 197 maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);198 199 for (const auto &C : I.Description)200 writeDescription(C, OS);201}202 203static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I,204 llvm::raw_ostream &OS) {205 if (I.Name == "")206 writeHeader("Global Namespace", 1, OS);207 else208 writeHeader("namespace " + I.Name, 1, OS);209 writeNewLine(OS);210 211 if (!I.Description.empty()) {212 for (const auto &C : I.Description)213 writeDescription(C, OS);214 writeNewLine(OS);215 }216 217 llvm::SmallString<64> BasePath = I.getRelativeFilePath("");218 219 if (!I.Children.Namespaces.empty()) {220 writeHeader("Namespaces", 2, OS);221 for (const auto &R : I.Children.Namespaces) {222 OS << "* ";223 writeNameLink(BasePath, R, OS);224 OS << "\n";225 }226 writeNewLine(OS);227 }228 229 if (!I.Children.Records.empty()) {230 writeHeader("Records", 2, OS);231 for (const auto &R : I.Children.Records) {232 OS << "* ";233 writeNameLink(BasePath, R, OS);234 OS << "\n";235 }236 writeNewLine(OS);237 }238 239 if (!I.Children.Functions.empty()) {240 writeHeader("Functions", 2, OS);241 for (const auto &F : I.Children.Functions)242 genMarkdown(CDCtx, F, OS);243 writeNewLine(OS);244 }245 if (!I.Children.Enums.empty()) {246 writeHeader("Enums", 2, OS);247 for (const auto &E : I.Children.Enums)248 genMarkdown(CDCtx, E, OS);249 writeNewLine(OS);250 }251}252 253static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,254 llvm::raw_ostream &OS) {255 writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);256 257 maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);258 259 if (!I.Description.empty()) {260 for (const auto &C : I.Description)261 writeDescription(C, OS);262 writeNewLine(OS);263 }264 265 std::string Parents = genReferenceList(I.Parents);266 std::string VParents = genReferenceList(I.VirtualParents);267 if (!Parents.empty() || !VParents.empty()) {268 if (Parents.empty())269 writeLine("Inherits from " + VParents, OS);270 else if (VParents.empty())271 writeLine("Inherits from " + Parents, OS);272 else273 writeLine("Inherits from " + Parents + ", " + VParents, OS);274 writeNewLine(OS);275 }276 277 if (!I.Members.empty()) {278 writeHeader("Members", 2, OS);279 for (const auto &Member : I.Members) {280 StringRef Access = getAccessSpelling(Member.Access);281 writeLine(Twine(Access) + (Access.empty() ? "" : " ") +282 (Member.IsStatic ? "static " : "") +283 Member.Type.Name.str() + " " + Member.Name.str(),284 OS);285 }286 writeNewLine(OS);287 }288 289 if (!I.Children.Records.empty()) {290 writeHeader("Records", 2, OS);291 for (const auto &R : I.Children.Records)292 writeLine(R.Name, OS);293 writeNewLine(OS);294 }295 if (!I.Children.Functions.empty()) {296 writeHeader("Functions", 2, OS);297 for (const auto &F : I.Children.Functions)298 genMarkdown(CDCtx, F, OS);299 writeNewLine(OS);300 }301 if (!I.Children.Enums.empty()) {302 writeHeader("Enums", 2, OS);303 for (const auto &E : I.Children.Enums)304 genMarkdown(CDCtx, E, OS);305 writeNewLine(OS);306 }307}308 309static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I,310 llvm::raw_ostream &OS) {311 // TODO support typedefs in markdown.312}313 314static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) {315 // Write out the heading level starting at ##316 OS << "##" << std::string(Level, '#') << " ";317 writeNameLink("", I, OS);318 OS << "\n";319}320 321static llvm::Error serializeIndex(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, "all_files.md");326 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text);327 if (FileErr)328 return llvm::createStringError(llvm::inconvertibleErrorCode(),329 "error creating index file: " +330 FileErr.message());331 332 CDCtx.Idx.sort();333 OS << "# All Files";334 if (!CDCtx.ProjectName.empty())335 OS << " for " << CDCtx.ProjectName;336 OS << "\n\n";337 338 for (auto C : CDCtx.Idx.Children)339 serializeReference(OS, C, 0);340 341 return llvm::Error::success();342}343 344static llvm::Error genIndex(ClangDocContext &CDCtx) {345 std::error_code FileErr;346 llvm::SmallString<128> FilePath;347 llvm::sys::path::native(CDCtx.OutDirectory, FilePath);348 llvm::sys::path::append(FilePath, "index.md");349 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text);350 if (FileErr)351 return llvm::createStringError(llvm::inconvertibleErrorCode(),352 "error creating index file: " +353 FileErr.message());354 CDCtx.Idx.sort();355 OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n";356 for (auto C : CDCtx.Idx.Children) {357 if (!C.Children.empty()) {358 const char *Type;359 switch (C.RefType) {360 case InfoType::IT_namespace:361 Type = "Namespace";362 break;363 case InfoType::IT_record:364 Type = "Type";365 break;366 case InfoType::IT_enum:367 Type = "Enum";368 break;369 case InfoType::IT_function:370 Type = "Function";371 break;372 case InfoType::IT_typedef:373 Type = "Typedef";374 break;375 case InfoType::IT_concept:376 Type = "Concept";377 break;378 case InfoType::IT_variable:379 Type = "Variable";380 break;381 case InfoType::IT_friend:382 Type = "Friend";383 break;384 case InfoType::IT_default:385 Type = "Other";386 }387 OS << "* " << Type << ": [" << C.Name << "](";388 if (!C.Path.empty())389 OS << C.Path << "/";390 OS << C.Name << ")\n";391 }392 }393 return llvm::Error::success();394}395 396/// Generator for Markdown documentation.397class MDGenerator : public Generator {398public:399 static const char *Format;400 401 llvm::Error generateDocumentation(402 StringRef RootDir, llvm::StringMap<std::unique_ptr<doc::Info>> Infos,403 const ClangDocContext &CDCtx, std::string DirName) override;404 llvm::Error createResources(ClangDocContext &CDCtx) override;405 llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,406 const ClangDocContext &CDCtx) override;407};408 409const char *MDGenerator::Format = "md";410 411llvm::Error MDGenerator::generateDocumentation(412 StringRef RootDir, llvm::StringMap<std::unique_ptr<doc::Info>> Infos,413 const ClangDocContext &CDCtx, std::string DirName) {414 // Track which directories we already tried to create.415 llvm::StringSet<> CreatedDirs;416 417 // Collect all output by file name and create the necessary directories.418 llvm::StringMap<std::vector<doc::Info *>> FileToInfos;419 for (const auto &Group : Infos) {420 doc::Info *Info = Group.getValue().get();421 422 llvm::SmallString<128> Path;423 llvm::sys::path::native(RootDir, Path);424 llvm::sys::path::append(Path, Info->getRelativeFilePath(""));425 if (!CreatedDirs.contains(Path)) {426 if (std::error_code Err = llvm::sys::fs::create_directories(Path);427 Err != std::error_code()) {428 return llvm::createStringError(Err, "Failed to create directory '%s'.",429 Path.c_str());430 }431 CreatedDirs.insert(Path);432 }433 434 llvm::sys::path::append(Path, Info->getFileBaseName() + ".md");435 FileToInfos[Path].push_back(Info);436 }437 438 for (const auto &Group : FileToInfos) {439 std::error_code FileErr;440 llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,441 llvm::sys::fs::OF_Text);442 if (FileErr) {443 return llvm::createStringError(FileErr, "Error opening file '%s'",444 Group.getKey().str().c_str());445 }446 447 for (const auto &Info : Group.getValue()) {448 if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {449 return Err;450 }451 }452 }453 454 return llvm::Error::success();455}456 457llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,458 const ClangDocContext &CDCtx) {459 switch (I->IT) {460 case InfoType::IT_namespace:461 genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS);462 break;463 case InfoType::IT_record:464 genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS);465 break;466 case InfoType::IT_enum:467 genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS);468 break;469 case InfoType::IT_function:470 genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS);471 break;472 case InfoType::IT_typedef:473 genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS);474 break;475 case InfoType::IT_concept:476 case InfoType::IT_variable:477 case InfoType::IT_friend:478 break;479 case InfoType::IT_default:480 return createStringError(llvm::inconvertibleErrorCode(),481 "unexpected InfoType");482 }483 return llvm::Error::success();484}485 486llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) {487 // Write an all_files.md488 auto Err = serializeIndex(CDCtx);489 if (Err)490 return Err;491 492 // Generate the index page.493 Err = genIndex(CDCtx);494 if (Err)495 return Err;496 497 return llvm::Error::success();498}499 500static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format,501 "Generator for MD output.");502 503// This anchor is used to force the linker to link in the generated object504// file and thus register the generator.505volatile int MDGeneratorAnchorSource = 0;506 507} // namespace doc508} // namespace clang509