| //===-- 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.Name + " " + N.Name; |
| First = false; |
| } |
| writeHeader(I.Name, 3, OS); |
| std::string Access = getAccessSpelling(I.Access).str(); |
| if (Access != "") |
| writeLine(genItalic(Access + " " + I.ReturnType.Type.Name + " " + I.Name + |
| "(" + Stream.str() + ")"), |
| OS); |
| else |
| writeLine(genItalic(I.ReturnType.Type.Name + " " + 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_None); |
| 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_None); |
| 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_None); |
| 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 |