diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp --- a/clang-tools-extra/clang-doc/MDGenerator.cpp +++ b/clang-tools-extra/clang-doc/MDGenerator.cpp @@ -20,6 +20,37 @@ // Markdown generation +static std::string genEscaped(StringRef Name) { + std::string Buffer; + llvm::raw_string_ostream Stream(Buffer); + for (unsigned I = 0, E = Name.size(); I != E; ++I) { + unsigned char C = Name[I]; + switch (C) { + case '\\': + case '`': + case '*': + case '_': + case '{': + case '}': + case '[': + case ']': + case '(': + case ')': + case '#': + case '+': + case '-': + case '.': + case '!': + Stream << '\\'; + LLVM_FALLTHROUGH; + default: + Stream << C; + break; + } + } + return Stream.str(); +} + static std::string genItalic(const Twine &Text) { return "*" + Text.str() + "*"; } @@ -28,6 +59,10 @@ return "**" + Text.str() + "**"; } +static std::string genCode(const Twine &Text) { + return "`" + Text.str() + "`"; +} + static std::string genReferenceList(const llvm::SmallVectorImpl &Refs) { std::string Buffer; @@ -40,19 +75,16 @@ 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 writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS, const Twine &Anchor = "") { + OS << std::string(Num, '#') << " " << genEscaped(Text.str()); + if (!Anchor.isTriviallyEmpty()) { + OS << " {#" << Anchor << "}"; + } + OS << "\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) << "*"; @@ -63,7 +95,7 @@ << std::to_string(L.LineNumber) << ")" << "*"; } - OS << "\n\n"; + OS << "\n"; } static void writeDescription(const CommentInfo &I, raw_ostream &OS) { @@ -73,28 +105,37 @@ } else if (I.Kind == "ParagraphComment") { for (const auto &Child : I.Children) writeDescription(*Child, OS); - writeNewLine(OS); + OS << "\n"; } else if (I.Kind == "BlockCommandComment") { + // TODO: @return is a block command and should be included in the "Returns" table. + // TODO: @see block commands should be grouped and rendered as "See Also" section. + // TODO: Figure out handling for @brief. + // TODO: What other block commands need special handling? OS << genEmphasis(I.Name); for (const auto &Child : I.Children) writeDescription(*Child, OS); } else if (I.Kind == "InlineCommandComment") { - OS << genEmphasis(I.Name) << " " << I.Text; + OS << genEmphasis(I.ParamName) << " " << I.Text; + for (const auto &Child : I.Children) + writeDescription(*Child, OS); } else if (I.Kind == "ParamCommandComment") { + // TODO: @param commands should included in the "Parameters" table. std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n"; } else if (I.Kind == "TParamCommandComment") { + // TODO: @tparam commands should included in the "Template Parameters" table. std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n"; } else if (I.Kind == "VerbatimBlockComment") { + // TODO: We should use ``` or indentation for verbatim blocks. for (const auto &Child : I.Children) writeDescription(*Child, OS); } else if (I.Kind == "VerbatimBlockLineComment") { - OS << I.Text; - writeNewLine(OS); + // TODO: We should use ` for verbatim block lines. + OS << I.Text << "\n"; } else if (I.Kind == "VerbatimLineComment") { - OS << I.Text; - writeNewLine(OS); + // TODO: We should use ` for verbatim lines. + OS << I.Text << "\n"; } else if (I.Kind == "HTMLStartTagComment") { if (I.AttrKeys.size() != I.AttrValues.size()) return; @@ -104,11 +145,11 @@ Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\""; std::string CloseTag = I.SelfClosing ? "/>" : ">"; - writeLine("<" + I.Name + Attrs.str() + CloseTag, OS); + OS << "<" << I.Name << Attrs.str() << CloseTag << "\n"; } else if (I.Kind == "HTMLEndTagComment") { - writeLine("", OS); + OS << "" << "\n"; } else if (I.Kind == "TextComment") { - OS << I.Text; + OS << I.Text.substr(1) << "\n"; } else { OS << "Unknown comment kind: " << I.Kind << ".\n\n"; } @@ -127,17 +168,17 @@ static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I, llvm::raw_ostream &OS) { if (I.Scoped) - writeLine("| enum class " + I.Name + " |", OS); + OS << "| enum class " << I.Name << " |" << "\n"; else - writeLine("| enum " + I.Name + " |", OS); - writeLine("--", OS); + OS << "| enum " << I.Name << " |" << "\n"; + OS << "--" << "\n"; std::string Buffer; llvm::raw_string_ostream Members(Buffer); if (!I.Members.empty()) for (const auto &N : I.Members) Members << "| " << N << " |\n"; - writeLine(Members.str(), OS); + OS << Members.str() << "\n"; if (I.DefLoc) writeFileDefinition(CDCtx, *I.DefLoc, OS); @@ -145,6 +186,18 @@ writeDescription(C, OS); } +static void genMarkdown(const ClangDocContext &CDCtx, const MemberTypeInfo &I, + llvm::raw_ostream &OS) { + writeHeader(I.Name, 3, OS, I.Name); + OS << "\n"; + std::string Access = getAccessSpelling(I.Access).str(); + if (Access != "") + OS << genCode(Access + " " + I.Type.Name + " " + I.Name) << "\n"; + else + OS << genCode(I.Type.Name + " " + I.Name) << "\n"; + OS << "\n"; +} + static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I, llvm::raw_ostream &OS) { std::string Buffer; @@ -157,20 +210,39 @@ First = false; } writeHeader(I.Name, 3, OS); + OS << "\n"; 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 (Access != "") { + OS << "```cpp\n"; + OS << Access + " " + I.ReturnType.Type.Name + " " + I.Name + "(" + Stream.str() + ")\n"; + OS << "```\n"; + } else { + OS << "```cpp\n"; + OS << I.ReturnType.Type.Name + " " + I.Name + "(" + Stream.str() + ")\n"; + OS << "```\n"; + } if (I.DefLoc) writeFileDefinition(CDCtx, *I.DefLoc, OS); + OS << "\n"; for (const auto &C : I.Description) writeDescription(C, OS); + + // Write function parameters as a table. + + OS << "| Parameters | |\n"; + OS << "| --- | --- |\n"; + for (const auto &N : I.Params) { + OS << "| " << genCode(N.Name) << " | " << genCode(N.Type.Name) << "|\n"; + } + OS << "\n"; + + // Write function return value as a table. + + OS << "| Returns | |\n"; + OS << "| --- | --- |\n"; + OS << "| " << genCode(I.ReturnType.Type.Name) << " | " << "" << "|\n"; + OS << "\n"; } static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I, @@ -179,12 +251,12 @@ writeHeader("Global Namespace", 1, OS); else writeHeader("namespace " + I.Name, 1, OS); - writeNewLine(OS); + OS << "\n"; if (!I.Description.empty()) { for (const auto &C : I.Description) writeDescription(C, OS); - writeNewLine(OS); + OS << "\n"; } llvm::SmallString<64> BasePath = I.getRelativeFilePath(""); @@ -196,7 +268,7 @@ writeNameLink(BasePath, R, OS); OS << "\n"; } - writeNewLine(OS); + OS << "\n"; } if (!I.ChildRecords.empty()) { @@ -206,76 +278,73 @@ writeNameLink(BasePath, R, OS); OS << "\n"; } - writeNewLine(OS); + OS << "\n"; } if (!I.ChildFunctions.empty()) { writeHeader("Functions", 2, OS); for (const auto &F : I.ChildFunctions) genMarkdown(CDCtx, F, OS); - writeNewLine(OS); + OS << "\n"; } if (!I.ChildEnums.empty()) { writeHeader("Enums", 2, OS); for (const auto &E : I.ChildEnums) genMarkdown(CDCtx, E, OS); - writeNewLine(OS); + OS << "\n"; } } static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I, llvm::raw_ostream &OS) { - writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS); + writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS, I.Name); if (I.DefLoc) writeFileDefinition(CDCtx, *I.DefLoc, OS); + OS << "\n"; if (!I.Description.empty()) { for (const auto &C : I.Description) writeDescription(C, OS); - writeNewLine(OS); + OS << "\n"; } 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); + OS << "Inherits from " << VParents << "\n"; else if (VParents.empty()) - writeLine("Inherits from " + Parents, OS); + OS << "Inherits from " << Parents << "\n"; else - writeLine("Inherits from " + Parents + ", " + VParents, OS); - writeNewLine(OS); + OS << "Inherits from " << Parents << ", " << VParents << "\n"; + OS << "\n"; } 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); + OS << "\n"; + for (const auto &Member : I.Members) + genMarkdown(CDCtx, Member, OS); + OS << "\n"; } if (!I.ChildRecords.empty()) { writeHeader("Records", 2, OS); for (const auto &R : I.ChildRecords) - writeLine(R.Name, OS); - writeNewLine(OS); + OS << R.Name << "\n"; + OS << "\n"; } if (!I.ChildFunctions.empty()) { writeHeader("Functions", 2, OS); for (const auto &F : I.ChildFunctions) genMarkdown(CDCtx, F, OS); - writeNewLine(OS); + OS << "\n"; } if (!I.ChildEnums.empty()) { writeHeader("Enums", 2, OS); for (const auto &E : I.ChildEnums) genMarkdown(CDCtx, E, OS); - writeNewLine(OS); + OS << "\n"; } } @@ -348,6 +417,43 @@ } return llvm::Error::success(); } + +static llvm::Error genTableOfContents(ClangDocContext &CDCtx) { + std::error_code FileErr; + llvm::SmallString<128> FilePath; + llvm::sys::path::native(CDCtx.OutDirectory, FilePath); + llvm::sys::path::append(FilePath, "_toc.yaml"); + llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None); + if (FileErr) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "error creating toc file: " + FileErr.message()); + + // Table of Contents has the following structure: + // + // toc: + // - title: namespace foo + // section: + // - title: Classes + // section: + // - title: Foo + // path: /path/to/foo.md + + CDCtx.Idx.sort(); + OS << "toc:\n"; + for (auto C : CDCtx.Idx.Children) { + OS << "- title: " << C.Name << "\n"; + llvm::SmallString<64> Path = C.getRelativeFilePath(""); // TODO: is this correct? + // 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, + C.getFileBaseName() + ".md"); + OS << " path: " << Path << "\n"; + + // TODO: Handle the nested elements. + } + return llvm::Error::success(); +} + /// Generator for Markdown documentation. class MDGenerator : public Generator { public: @@ -362,6 +468,7 @@ llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, const ClangDocContext &CDCtx) { + OS << "[TOC]\n\n"; switch (I->IT) { case InfoType::IT_namespace: genMarkdown(CDCtx, *static_cast(I), OS); @@ -393,6 +500,10 @@ if (Err) return Err; + Err = genTableOfContents(CDCtx); + if (Err) + return Err; + return llvm::Error::success(); }