|  | //===-- MDGenerator.cpp - Markdown Generator --------------------*- C++ -*-===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "Generators.h" | 
|  | #include "Representation.h" | 
|  | #include "llvm/ADT/StringRef.h" | 
|  | #include "llvm/Support/FileSystem.h" | 
|  | #include "llvm/Support/Path.h" | 
|  | #include <string> | 
|  |  | 
|  | using namespace llvm; | 
|  |  | 
|  | namespace clang { | 
|  | namespace doc { | 
|  |  | 
|  | // Markdown generation | 
|  |  | 
|  | static std::string genItalic(const Twine &Text) { | 
|  | return "*" + Text.str() + "*"; | 
|  | } | 
|  |  | 
|  | static std::string genEmphasis(const Twine &Text) { | 
|  | return "**" + Text.str() + "**"; | 
|  | } | 
|  |  | 
|  | static std::string | 
|  | genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) { | 
|  | std::string Buffer; | 
|  | llvm::raw_string_ostream Stream(Buffer); | 
|  | for (const auto &R : Refs) { | 
|  | if (&R != Refs.begin()) | 
|  | Stream << ", "; | 
|  | Stream << R.Name; | 
|  | } | 
|  | return Stream.str(); | 
|  | } | 
|  |  | 
|  | static void writeLine(const Twine &Text, raw_ostream &OS) { | 
|  | OS << Text << "\n\n"; | 
|  | } | 
|  |  | 
|  | static void writeNewLine(raw_ostream &OS) { OS << "\n\n"; } | 
|  |  | 
|  | static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) { | 
|  | OS << std::string(Num, '#') + " " + Text << "\n\n"; | 
|  | } | 
|  |  | 
|  | static void writeFileDefinition(const ClangDocContext &CDCtx, const Location &L, | 
|  | raw_ostream &OS) { | 
|  |  | 
|  | if (!CDCtx.RepositoryUrl) { | 
|  | OS << "*Defined at " << L.Filename << "#" << std::to_string(L.LineNumber) | 
|  | << "*"; | 
|  | } else { | 
|  | OS << "*Defined at [" << L.Filename << "#" << std::to_string(L.LineNumber) | 
|  | << "](" << StringRef{*CDCtx.RepositoryUrl} | 
|  | << llvm::sys::path::relative_path(L.Filename) << "#" | 
|  | << std::to_string(L.LineNumber) << ")" | 
|  | << "*"; | 
|  | } | 
|  | OS << "\n\n"; | 
|  | } | 
|  |  | 
|  | static void writeDescription(const CommentInfo &I, raw_ostream &OS) { | 
|  | if (I.Kind == "FullComment") { | 
|  | for (const auto &Child : I.Children) | 
|  | writeDescription(*Child, OS); | 
|  | } else if (I.Kind == "ParagraphComment") { | 
|  | for (const auto &Child : I.Children) | 
|  | writeDescription(*Child, OS); | 
|  | writeNewLine(OS); | 
|  | } else if (I.Kind == "BlockCommandComment") { | 
|  | OS << genEmphasis(I.Name); | 
|  | for (const auto &Child : I.Children) | 
|  | writeDescription(*Child, OS); | 
|  | } else if (I.Kind == "InlineCommandComment") { | 
|  | OS << genEmphasis(I.Name) << " " << I.Text; | 
|  | } else if (I.Kind == "ParamCommandComment") { | 
|  | std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; | 
|  | OS << genEmphasis(I.ParamName) << I.Text << Direction; | 
|  | for (const auto &Child : I.Children) | 
|  | writeDescription(*Child, OS); | 
|  | } else if (I.Kind == "TParamCommandComment") { | 
|  | std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; | 
|  | OS << genEmphasis(I.ParamName) << I.Text << Direction; | 
|  | for (const auto &Child : I.Children) | 
|  | writeDescription(*Child, OS); | 
|  | } else if (I.Kind == "VerbatimBlockComment") { | 
|  | for (const auto &Child : I.Children) | 
|  | writeDescription(*Child, OS); | 
|  | } else if (I.Kind == "VerbatimBlockLineComment") { | 
|  | OS << I.Text; | 
|  | writeNewLine(OS); | 
|  | } else if (I.Kind == "VerbatimLineComment") { | 
|  | OS << I.Text; | 
|  | writeNewLine(OS); | 
|  | } else if (I.Kind == "HTMLStartTagComment") { | 
|  | if (I.AttrKeys.size() != I.AttrValues.size()) | 
|  | return; | 
|  | std::string Buffer; | 
|  | llvm::raw_string_ostream Attrs(Buffer); | 
|  | for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx) | 
|  | Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\""; | 
|  |  | 
|  | std::string CloseTag = I.SelfClosing ? "/>" : ">"; | 
|  | writeLine("<" + I.Name + Attrs.str() + CloseTag, OS); | 
|  | } else if (I.Kind == "HTMLEndTagComment") { | 
|  | writeLine("</" + I.Name + ">", OS); | 
|  | } else if (I.Kind == "TextComment") { | 
|  | OS << I.Text; | 
|  | } else { | 
|  | OS << "Unknown comment kind: " << I.Kind << ".\n\n"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void writeNameLink(const StringRef &CurrentPath, const Reference &R, | 
|  | llvm::raw_ostream &OS) { | 
|  | llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath); | 
|  | // Paths in Markdown use POSIX separators. | 
|  | llvm::sys::path::native(Path, llvm::sys::path::Style::posix); | 
|  | llvm::sys::path::append(Path, llvm::sys::path::Style::posix, | 
|  | R.getFileBaseName() + ".md"); | 
|  | OS << "[" << R.Name << "](" << Path << ")"; | 
|  | } | 
|  |  | 
|  | static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I, | 
|  | llvm::raw_ostream &OS) { | 
|  | if (I.Scoped) | 
|  | writeLine("| enum class " + I.Name + " |", OS); | 
|  | else | 
|  | writeLine("| enum " + I.Name + " |", OS); | 
|  | writeLine("--", OS); | 
|  |  | 
|  | std::string Buffer; | 
|  | llvm::raw_string_ostream Members(Buffer); | 
|  | if (!I.Members.empty()) | 
|  | for (const auto &N : I.Members) | 
|  | Members << "| " << N.Name << " |\n"; | 
|  | writeLine(Members.str(), OS); | 
|  | if (I.DefLoc) | 
|  | writeFileDefinition(CDCtx, *I.DefLoc, OS); | 
|  |  | 
|  | for (const auto &C : I.Description) | 
|  | writeDescription(C, OS); | 
|  | } | 
|  |  | 
|  | static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I, | 
|  | llvm::raw_ostream &OS) { | 
|  | std::string Buffer; | 
|  | llvm::raw_string_ostream Stream(Buffer); | 
|  | bool First = true; | 
|  | for (const auto &N : I.Params) { | 
|  | if (!First) | 
|  | Stream << ", "; | 
|  | Stream << N.Type.QualName + " " + N.Name; | 
|  | First = false; | 
|  | } | 
|  | writeHeader(I.Name, 3, OS); | 
|  | std::string Access = getAccessSpelling(I.Access).str(); | 
|  | if (Access != "") | 
|  | writeLine(genItalic(Access + " " + I.ReturnType.Type.QualName + " " + | 
|  | I.Name + "(" + Stream.str() + ")"), | 
|  | OS); | 
|  | else | 
|  | writeLine(genItalic(I.ReturnType.Type.QualName + " " + I.Name + "(" + | 
|  | Stream.str() + ")"), | 
|  | OS); | 
|  | if (I.DefLoc) | 
|  | writeFileDefinition(CDCtx, *I.DefLoc, OS); | 
|  |  | 
|  | for (const auto &C : I.Description) | 
|  | writeDescription(C, OS); | 
|  | } | 
|  |  | 
|  | static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I, | 
|  | llvm::raw_ostream &OS) { | 
|  | if (I.Name == "") | 
|  | writeHeader("Global Namespace", 1, OS); | 
|  | else | 
|  | writeHeader("namespace " + I.Name, 1, OS); | 
|  | writeNewLine(OS); | 
|  |  | 
|  | if (!I.Description.empty()) { | 
|  | for (const auto &C : I.Description) | 
|  | writeDescription(C, OS); | 
|  | writeNewLine(OS); | 
|  | } | 
|  |  | 
|  | llvm::SmallString<64> BasePath = I.getRelativeFilePath(""); | 
|  |  | 
|  | if (!I.Children.Namespaces.empty()) { | 
|  | writeHeader("Namespaces", 2, OS); | 
|  | for (const auto &R : I.Children.Namespaces) { | 
|  | OS << "* "; | 
|  | writeNameLink(BasePath, R, OS); | 
|  | OS << "\n"; | 
|  | } | 
|  | writeNewLine(OS); | 
|  | } | 
|  |  | 
|  | if (!I.Children.Records.empty()) { | 
|  | writeHeader("Records", 2, OS); | 
|  | for (const auto &R : I.Children.Records) { | 
|  | OS << "* "; | 
|  | writeNameLink(BasePath, R, OS); | 
|  | OS << "\n"; | 
|  | } | 
|  | writeNewLine(OS); | 
|  | } | 
|  |  | 
|  | if (!I.Children.Functions.empty()) { | 
|  | writeHeader("Functions", 2, OS); | 
|  | for (const auto &F : I.Children.Functions) | 
|  | genMarkdown(CDCtx, F, OS); | 
|  | writeNewLine(OS); | 
|  | } | 
|  | if (!I.Children.Enums.empty()) { | 
|  | writeHeader("Enums", 2, OS); | 
|  | for (const auto &E : I.Children.Enums) | 
|  | genMarkdown(CDCtx, E, OS); | 
|  | writeNewLine(OS); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I, | 
|  | llvm::raw_ostream &OS) { | 
|  | writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS); | 
|  | if (I.DefLoc) | 
|  | writeFileDefinition(CDCtx, *I.DefLoc, OS); | 
|  |  | 
|  | if (!I.Description.empty()) { | 
|  | for (const auto &C : I.Description) | 
|  | writeDescription(C, OS); | 
|  | writeNewLine(OS); | 
|  | } | 
|  |  | 
|  | std::string Parents = genReferenceList(I.Parents); | 
|  | std::string VParents = genReferenceList(I.VirtualParents); | 
|  | if (!Parents.empty() || !VParents.empty()) { | 
|  | if (Parents.empty()) | 
|  | writeLine("Inherits from " + VParents, OS); | 
|  | else if (VParents.empty()) | 
|  | writeLine("Inherits from " + Parents, OS); | 
|  | else | 
|  | writeLine("Inherits from " + Parents + ", " + VParents, OS); | 
|  | writeNewLine(OS); | 
|  | } | 
|  |  | 
|  | if (!I.Members.empty()) { | 
|  | writeHeader("Members", 2, OS); | 
|  | for (const auto &Member : I.Members) { | 
|  | std::string Access = getAccessSpelling(Member.Access).str(); | 
|  | if (Access != "") | 
|  | writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS); | 
|  | else | 
|  | writeLine(Member.Type.Name + " " + Member.Name, OS); | 
|  | } | 
|  | writeNewLine(OS); | 
|  | } | 
|  |  | 
|  | if (!I.Children.Records.empty()) { | 
|  | writeHeader("Records", 2, OS); | 
|  | for (const auto &R : I.Children.Records) | 
|  | writeLine(R.Name, OS); | 
|  | writeNewLine(OS); | 
|  | } | 
|  | if (!I.Children.Functions.empty()) { | 
|  | writeHeader("Functions", 2, OS); | 
|  | for (const auto &F : I.Children.Functions) | 
|  | genMarkdown(CDCtx, F, OS); | 
|  | writeNewLine(OS); | 
|  | } | 
|  | if (!I.Children.Enums.empty()) { | 
|  | writeHeader("Enums", 2, OS); | 
|  | for (const auto &E : I.Children.Enums) | 
|  | genMarkdown(CDCtx, E, OS); | 
|  | writeNewLine(OS); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I, | 
|  | llvm::raw_ostream &OS) { | 
|  | // TODO support typedefs in markdown. | 
|  | } | 
|  |  | 
|  | static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) { | 
|  | // Write out the heading level starting at ## | 
|  | OS << "##" << std::string(Level, '#') << " "; | 
|  | writeNameLink("", I, OS); | 
|  | OS << "\n"; | 
|  | } | 
|  |  | 
|  | static llvm::Error serializeIndex(ClangDocContext &CDCtx) { | 
|  | std::error_code FileErr; | 
|  | llvm::SmallString<128> FilePath; | 
|  | llvm::sys::path::native(CDCtx.OutDirectory, FilePath); | 
|  | llvm::sys::path::append(FilePath, "all_files.md"); | 
|  | llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text); | 
|  | if (FileErr) | 
|  | return llvm::createStringError(llvm::inconvertibleErrorCode(), | 
|  | "error creating index file: " + | 
|  | FileErr.message()); | 
|  |  | 
|  | CDCtx.Idx.sort(); | 
|  | OS << "# All Files"; | 
|  | if (!CDCtx.ProjectName.empty()) | 
|  | OS << " for " << CDCtx.ProjectName; | 
|  | OS << "\n\n"; | 
|  |  | 
|  | for (auto C : CDCtx.Idx.Children) | 
|  | serializeReference(OS, C, 0); | 
|  |  | 
|  | return llvm::Error::success(); | 
|  | } | 
|  |  | 
|  | static llvm::Error genIndex(ClangDocContext &CDCtx) { | 
|  | std::error_code FileErr; | 
|  | llvm::SmallString<128> FilePath; | 
|  | llvm::sys::path::native(CDCtx.OutDirectory, FilePath); | 
|  | llvm::sys::path::append(FilePath, "index.md"); | 
|  | llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text); | 
|  | if (FileErr) | 
|  | return llvm::createStringError(llvm::inconvertibleErrorCode(), | 
|  | "error creating index file: " + | 
|  | FileErr.message()); | 
|  | CDCtx.Idx.sort(); | 
|  | OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n"; | 
|  | for (auto C : CDCtx.Idx.Children) { | 
|  | if (!C.Children.empty()) { | 
|  | const char *Type; | 
|  | switch (C.RefType) { | 
|  | case InfoType::IT_namespace: | 
|  | Type = "Namespace"; | 
|  | break; | 
|  | case InfoType::IT_record: | 
|  | Type = "Type"; | 
|  | break; | 
|  | case InfoType::IT_enum: | 
|  | Type = "Enum"; | 
|  | break; | 
|  | case InfoType::IT_function: | 
|  | Type = "Function"; | 
|  | break; | 
|  | case InfoType::IT_typedef: | 
|  | Type = "Typedef"; | 
|  | break; | 
|  | case InfoType::IT_default: | 
|  | Type = "Other"; | 
|  | } | 
|  | OS << "* " << Type << ": [" << C.Name << "]("; | 
|  | if (!C.Path.empty()) | 
|  | OS << C.Path << "/"; | 
|  | OS << C.Name << ")\n"; | 
|  | } | 
|  | } | 
|  | return llvm::Error::success(); | 
|  | } | 
|  |  | 
|  | /// Generator for Markdown documentation. | 
|  | class MDGenerator : public Generator { | 
|  | public: | 
|  | static const char *Format; | 
|  |  | 
|  | llvm::Error generateDocs(StringRef RootDir, | 
|  | llvm::StringMap<std::unique_ptr<doc::Info>> Infos, | 
|  | const ClangDocContext &CDCtx) override; | 
|  | llvm::Error createResources(ClangDocContext &CDCtx) override; | 
|  | llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, | 
|  | const ClangDocContext &CDCtx) override; | 
|  | }; | 
|  |  | 
|  | const char *MDGenerator::Format = "md"; | 
|  |  | 
|  | llvm::Error | 
|  | MDGenerator::generateDocs(StringRef RootDir, | 
|  | llvm::StringMap<std::unique_ptr<doc::Info>> Infos, | 
|  | const ClangDocContext &CDCtx) { | 
|  | // Track which directories we already tried to create. | 
|  | llvm::StringSet<> CreatedDirs; | 
|  |  | 
|  | // Collect all output by file name and create the necessary directories. | 
|  | llvm::StringMap<std::vector<doc::Info *>> FileToInfos; | 
|  | for (const auto &Group : Infos) { | 
|  | doc::Info *Info = Group.getValue().get(); | 
|  |  | 
|  | llvm::SmallString<128> Path; | 
|  | llvm::sys::path::native(RootDir, Path); | 
|  | llvm::sys::path::append(Path, Info->getRelativeFilePath("")); | 
|  | if (!CreatedDirs.contains(Path)) { | 
|  | if (std::error_code Err = llvm::sys::fs::create_directories(Path); | 
|  | Err != std::error_code()) { | 
|  | return llvm::createStringError(Err, "Failed to create directory '%s'.", | 
|  | Path.c_str()); | 
|  | } | 
|  | CreatedDirs.insert(Path); | 
|  | } | 
|  |  | 
|  | llvm::sys::path::append(Path, Info->getFileBaseName() + ".md"); | 
|  | FileToInfos[Path].push_back(Info); | 
|  | } | 
|  |  | 
|  | for (const auto &Group : FileToInfos) { | 
|  | std::error_code FileErr; | 
|  | llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, | 
|  | llvm::sys::fs::OF_Text); | 
|  | if (FileErr) { | 
|  | return llvm::createStringError(FileErr, "Error opening file '%s'", | 
|  | Group.getKey().str().c_str()); | 
|  | } | 
|  |  | 
|  | for (const auto &Info : Group.getValue()) { | 
|  | if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) { | 
|  | return Err; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return llvm::Error::success(); | 
|  | } | 
|  |  | 
|  | llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, | 
|  | const ClangDocContext &CDCtx) { | 
|  | switch (I->IT) { | 
|  | case InfoType::IT_namespace: | 
|  | genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS); | 
|  | break; | 
|  | case InfoType::IT_record: | 
|  | genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS); | 
|  | break; | 
|  | case InfoType::IT_enum: | 
|  | genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS); | 
|  | break; | 
|  | case InfoType::IT_function: | 
|  | genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS); | 
|  | break; | 
|  | case InfoType::IT_typedef: | 
|  | genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS); | 
|  | break; | 
|  | case InfoType::IT_default: | 
|  | return createStringError(llvm::inconvertibleErrorCode(), | 
|  | "unexpected InfoType"); | 
|  | } | 
|  | return llvm::Error::success(); | 
|  | } | 
|  |  | 
|  | llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) { | 
|  | // Write an all_files.md | 
|  | auto Err = serializeIndex(CDCtx); | 
|  | if (Err) | 
|  | return Err; | 
|  |  | 
|  | // Generate the index page. | 
|  | Err = genIndex(CDCtx); | 
|  | if (Err) | 
|  | return Err; | 
|  |  | 
|  | return llvm::Error::success(); | 
|  | } | 
|  |  | 
|  | static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format, | 
|  | "Generator for MD output."); | 
|  |  | 
|  | // This anchor is used to force the linker to link in the generated object | 
|  | // file and thus register the generator. | 
|  | volatile int MDGeneratorAnchorSource = 0; | 
|  |  | 
|  | } // namespace doc | 
|  | } // namespace clang |