phosek created this revision.
phosek added reviewers: abrachet, paulkirth.
Herald added a project: All.
phosek requested review of this revision.
Herald added a project: clang-tools-extra.
Herald added a subscriber: cfe-commits.

This change tries to improve the usability of Markdown output, taking
the inspiration from other documentation generators where appropriate.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D130231

Files:
  clang-tools-extra/clang-doc/MDGenerator.cpp

Index: clang-tools-extra/clang-doc/MDGenerator.cpp
===================================================================
--- clang-tools-extra/clang-doc/MDGenerator.cpp
+++ 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<Reference> &Refs) {
   std::string Buffer;
@@ -40,21 +75,18 @@
   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)
+    OS << "*foobar Defined at " << L.Filename << "#" << std::to_string(L.LineNumber)
        << "*";
   } else {
     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("</" + I.Name + ">", OS);
+    OS << "</" << I.Name << ">" << "\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<clang::doc::NamespaceInfo *>(I), OS);
@@ -393,6 +500,10 @@
   if (Err)
     return Err;
 
+  Err = genTableOfContents(CDCtx);
+  if (Err)
+    return Err;
+
   return llvm::Error::success();
 }
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to