MDGenerator.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  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-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. #include "Generators.h"
  9. #include "Representation.h"
  10. #include "llvm/ADT/StringRef.h"
  11. #include "llvm/Support/FileSystem.h"
  12. #include "llvm/Support/Path.h"
  13. #include <string>
  14. using namespace llvm;
  15. namespace clang {
  16. namespace doc {
  17. // Markdown generation
  18. static std::string genItalic(const Twine &Text) {
  19. return "*" + Text.str() + "*";
  20. }
  21. static std::string genEmphasis(const Twine &Text) {
  22. return "**" + Text.str() + "**";
  23. }
  24. static std::string
  25. genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) {
  26. std::string Buffer;
  27. llvm::raw_string_ostream Stream(Buffer);
  28. for (const auto &R : Refs) {
  29. if (&R != Refs.begin())
  30. Stream << ", ";
  31. Stream << R.Name;
  32. }
  33. return Stream.str();
  34. }
  35. static void writeLine(const Twine &Text, raw_ostream &OS) {
  36. OS << Text << "\n\n";
  37. }
  38. static void writeNewLine(raw_ostream &OS) { OS << "\n\n"; }
  39. static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
  40. OS << std::string(Num, '#') + " " + Text << "\n\n";
  41. }
  42. static void writeFileDefinition(const ClangDocContext &CDCtx, const Location &L,
  43. raw_ostream &OS) {
  44. if (!CDCtx.RepositoryUrl) {
  45. OS << "*Defined at " << L.Filename << "#" << std::to_string(L.LineNumber)
  46. << "*";
  47. } else {
  48. OS << "*Defined at [" << L.Filename << "#" << std::to_string(L.LineNumber)
  49. << "](" << StringRef{*CDCtx.RepositoryUrl}
  50. << llvm::sys::path::relative_path(L.Filename) << "#"
  51. << std::to_string(L.LineNumber) << ")"
  52. << "*";
  53. }
  54. OS << "\n\n";
  55. }
  56. static void writeDescription(const CommentInfo &I, raw_ostream &OS) {
  57. if (I.Kind == "FullComment") {
  58. for (const auto &Child : I.Children)
  59. writeDescription(*Child, OS);
  60. } else if (I.Kind == "ParagraphComment") {
  61. for (const auto &Child : I.Children)
  62. writeDescription(*Child, OS);
  63. writeNewLine(OS);
  64. } else if (I.Kind == "BlockCommandComment") {
  65. OS << genEmphasis(I.Name);
  66. for (const auto &Child : I.Children)
  67. writeDescription(*Child, OS);
  68. } else if (I.Kind == "InlineCommandComment") {
  69. OS << genEmphasis(I.Name) << " " << I.Text;
  70. } else if (I.Kind == "ParamCommandComment") {
  71. std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
  72. OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n";
  73. } else if (I.Kind == "TParamCommandComment") {
  74. std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
  75. OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n";
  76. } else if (I.Kind == "VerbatimBlockComment") {
  77. for (const auto &Child : I.Children)
  78. writeDescription(*Child, OS);
  79. } else if (I.Kind == "VerbatimBlockLineComment") {
  80. OS << I.Text;
  81. writeNewLine(OS);
  82. } else if (I.Kind == "VerbatimLineComment") {
  83. OS << I.Text;
  84. writeNewLine(OS);
  85. } else if (I.Kind == "HTMLStartTagComment") {
  86. if (I.AttrKeys.size() != I.AttrValues.size())
  87. return;
  88. std::string Buffer;
  89. llvm::raw_string_ostream Attrs(Buffer);
  90. for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx)
  91. Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\"";
  92. std::string CloseTag = I.SelfClosing ? "/>" : ">";
  93. writeLine("<" + I.Name + Attrs.str() + CloseTag, OS);
  94. } else if (I.Kind == "HTMLEndTagComment") {
  95. writeLine("</" + I.Name + ">", OS);
  96. } else if (I.Kind == "TextComment") {
  97. OS << I.Text;
  98. } else {
  99. OS << "Unknown comment kind: " << I.Kind << ".\n\n";
  100. }
  101. }
  102. static void writeNameLink(const StringRef &CurrentPath, const Reference &R,
  103. llvm::raw_ostream &OS) {
  104. llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath);
  105. // Paths in Markdown use POSIX separators.
  106. llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
  107. llvm::sys::path::append(Path, llvm::sys::path::Style::posix,
  108. R.getFileBaseName() + ".md");
  109. OS << "[" << R.Name << "](" << Path << ")";
  110. }
  111. static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I,
  112. llvm::raw_ostream &OS) {
  113. if (I.Scoped)
  114. writeLine("| enum class " + I.Name + " |", OS);
  115. else
  116. writeLine("| enum " + I.Name + " |", OS);
  117. writeLine("--", OS);
  118. std::string Buffer;
  119. llvm::raw_string_ostream Members(Buffer);
  120. if (!I.Members.empty())
  121. for (const auto &N : I.Members)
  122. Members << "| " << N.Name << " |\n";
  123. writeLine(Members.str(), OS);
  124. if (I.DefLoc)
  125. writeFileDefinition(CDCtx, *I.DefLoc, OS);
  126. for (const auto &C : I.Description)
  127. writeDescription(C, OS);
  128. }
  129. static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
  130. llvm::raw_ostream &OS) {
  131. std::string Buffer;
  132. llvm::raw_string_ostream Stream(Buffer);
  133. bool First = true;
  134. for (const auto &N : I.Params) {
  135. if (!First)
  136. Stream << ", ";
  137. Stream << N.Type.Name + " " + N.Name;
  138. First = false;
  139. }
  140. writeHeader(I.Name, 3, OS);
  141. std::string Access = getAccessSpelling(I.Access).str();
  142. if (Access != "")
  143. writeLine(genItalic(Access + " " + I.ReturnType.Type.Name + " " + I.Name +
  144. "(" + Stream.str() + ")"),
  145. OS);
  146. else
  147. writeLine(genItalic(I.ReturnType.Type.Name + " " + I.Name + "(" +
  148. Stream.str() + ")"),
  149. OS);
  150. if (I.DefLoc)
  151. writeFileDefinition(CDCtx, *I.DefLoc, OS);
  152. for (const auto &C : I.Description)
  153. writeDescription(C, OS);
  154. }
  155. static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I,
  156. llvm::raw_ostream &OS) {
  157. if (I.Name == "")
  158. writeHeader("Global Namespace", 1, OS);
  159. else
  160. writeHeader("namespace " + I.Name, 1, OS);
  161. writeNewLine(OS);
  162. if (!I.Description.empty()) {
  163. for (const auto &C : I.Description)
  164. writeDescription(C, OS);
  165. writeNewLine(OS);
  166. }
  167. llvm::SmallString<64> BasePath = I.getRelativeFilePath("");
  168. if (!I.Children.Namespaces.empty()) {
  169. writeHeader("Namespaces", 2, OS);
  170. for (const auto &R : I.Children.Namespaces) {
  171. OS << "* ";
  172. writeNameLink(BasePath, R, OS);
  173. OS << "\n";
  174. }
  175. writeNewLine(OS);
  176. }
  177. if (!I.Children.Records.empty()) {
  178. writeHeader("Records", 2, OS);
  179. for (const auto &R : I.Children.Records) {
  180. OS << "* ";
  181. writeNameLink(BasePath, R, OS);
  182. OS << "\n";
  183. }
  184. writeNewLine(OS);
  185. }
  186. if (!I.Children.Functions.empty()) {
  187. writeHeader("Functions", 2, OS);
  188. for (const auto &F : I.Children.Functions)
  189. genMarkdown(CDCtx, F, OS);
  190. writeNewLine(OS);
  191. }
  192. if (!I.Children.Enums.empty()) {
  193. writeHeader("Enums", 2, OS);
  194. for (const auto &E : I.Children.Enums)
  195. genMarkdown(CDCtx, E, OS);
  196. writeNewLine(OS);
  197. }
  198. }
  199. static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
  200. llvm::raw_ostream &OS) {
  201. writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
  202. if (I.DefLoc)
  203. writeFileDefinition(CDCtx, *I.DefLoc, OS);
  204. if (!I.Description.empty()) {
  205. for (const auto &C : I.Description)
  206. writeDescription(C, OS);
  207. writeNewLine(OS);
  208. }
  209. std::string Parents = genReferenceList(I.Parents);
  210. std::string VParents = genReferenceList(I.VirtualParents);
  211. if (!Parents.empty() || !VParents.empty()) {
  212. if (Parents.empty())
  213. writeLine("Inherits from " + VParents, OS);
  214. else if (VParents.empty())
  215. writeLine("Inherits from " + Parents, OS);
  216. else
  217. writeLine("Inherits from " + Parents + ", " + VParents, OS);
  218. writeNewLine(OS);
  219. }
  220. if (!I.Members.empty()) {
  221. writeHeader("Members", 2, OS);
  222. for (const auto &Member : I.Members) {
  223. std::string Access = getAccessSpelling(Member.Access).str();
  224. if (Access != "")
  225. writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS);
  226. else
  227. writeLine(Member.Type.Name + " " + Member.Name, OS);
  228. }
  229. writeNewLine(OS);
  230. }
  231. if (!I.Children.Records.empty()) {
  232. writeHeader("Records", 2, OS);
  233. for (const auto &R : I.Children.Records)
  234. writeLine(R.Name, OS);
  235. writeNewLine(OS);
  236. }
  237. if (!I.Children.Functions.empty()) {
  238. writeHeader("Functions", 2, OS);
  239. for (const auto &F : I.Children.Functions)
  240. genMarkdown(CDCtx, F, OS);
  241. writeNewLine(OS);
  242. }
  243. if (!I.Children.Enums.empty()) {
  244. writeHeader("Enums", 2, OS);
  245. for (const auto &E : I.Children.Enums)
  246. genMarkdown(CDCtx, E, OS);
  247. writeNewLine(OS);
  248. }
  249. }
  250. static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I,
  251. llvm::raw_ostream &OS) {
  252. // TODO support typedefs in markdown.
  253. }
  254. static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) {
  255. // Write out the heading level starting at ##
  256. OS << "##" << std::string(Level, '#') << " ";
  257. writeNameLink("", I, OS);
  258. OS << "\n";
  259. }
  260. static llvm::Error serializeIndex(ClangDocContext &CDCtx) {
  261. std::error_code FileErr;
  262. llvm::SmallString<128> FilePath;
  263. llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
  264. llvm::sys::path::append(FilePath, "all_files.md");
  265. llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
  266. if (FileErr)
  267. return llvm::createStringError(llvm::inconvertibleErrorCode(),
  268. "error creating index file: " +
  269. FileErr.message());
  270. CDCtx.Idx.sort();
  271. OS << "# All Files";
  272. if (!CDCtx.ProjectName.empty())
  273. OS << " for " << CDCtx.ProjectName;
  274. OS << "\n\n";
  275. for (auto C : CDCtx.Idx.Children)
  276. serializeReference(OS, C, 0);
  277. return llvm::Error::success();
  278. }
  279. static llvm::Error genIndex(ClangDocContext &CDCtx) {
  280. std::error_code FileErr;
  281. llvm::SmallString<128> FilePath;
  282. llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
  283. llvm::sys::path::append(FilePath, "index.md");
  284. llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
  285. if (FileErr)
  286. return llvm::createStringError(llvm::inconvertibleErrorCode(),
  287. "error creating index file: " +
  288. FileErr.message());
  289. CDCtx.Idx.sort();
  290. OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n";
  291. for (auto C : CDCtx.Idx.Children) {
  292. if (!C.Children.empty()) {
  293. const char *Type;
  294. switch (C.RefType) {
  295. case InfoType::IT_namespace:
  296. Type = "Namespace";
  297. break;
  298. case InfoType::IT_record:
  299. Type = "Type";
  300. break;
  301. case InfoType::IT_enum:
  302. Type = "Enum";
  303. break;
  304. case InfoType::IT_function:
  305. Type = "Function";
  306. break;
  307. case InfoType::IT_typedef:
  308. Type = "Typedef";
  309. break;
  310. case InfoType::IT_default:
  311. Type = "Other";
  312. }
  313. OS << "* " << Type << ": [" << C.Name << "](";
  314. if (!C.Path.empty())
  315. OS << C.Path << "/";
  316. OS << C.Name << ")\n";
  317. }
  318. }
  319. return llvm::Error::success();
  320. }
  321. /// Generator for Markdown documentation.
  322. class MDGenerator : public Generator {
  323. public:
  324. static const char *Format;
  325. llvm::Error generateDocs(StringRef RootDir,
  326. llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
  327. const ClangDocContext &CDCtx) override;
  328. llvm::Error createResources(ClangDocContext &CDCtx) override;
  329. llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
  330. const ClangDocContext &CDCtx) override;
  331. };
  332. const char *MDGenerator::Format = "md";
  333. llvm::Error
  334. MDGenerator::generateDocs(StringRef RootDir,
  335. llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
  336. const ClangDocContext &CDCtx) {
  337. // Track which directories we already tried to create.
  338. llvm::StringSet<> CreatedDirs;
  339. // Collect all output by file name and create the necessary directories.
  340. llvm::StringMap<std::vector<doc::Info *>> FileToInfos;
  341. for (const auto &Group : Infos) {
  342. doc::Info *Info = Group.getValue().get();
  343. llvm::SmallString<128> Path;
  344. llvm::sys::path::native(RootDir, Path);
  345. llvm::sys::path::append(Path, Info->getRelativeFilePath(""));
  346. if (CreatedDirs.find(Path) == CreatedDirs.end()) {
  347. if (std::error_code Err = llvm::sys::fs::create_directories(Path);
  348. Err != std::error_code()) {
  349. return llvm::createStringError(Err, "Failed to create directory '%s'.",
  350. Path.c_str());
  351. }
  352. CreatedDirs.insert(Path);
  353. }
  354. llvm::sys::path::append(Path, Info->getFileBaseName() + ".md");
  355. FileToInfos[Path].push_back(Info);
  356. }
  357. for (const auto &Group : FileToInfos) {
  358. std::error_code FileErr;
  359. llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,
  360. llvm::sys::fs::OF_None);
  361. if (FileErr) {
  362. return llvm::createStringError(FileErr, "Error opening file '%s'",
  363. Group.getKey().str().c_str());
  364. }
  365. for (const auto &Info : Group.getValue()) {
  366. if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {
  367. return Err;
  368. }
  369. }
  370. }
  371. return llvm::Error::success();
  372. }
  373. llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
  374. const ClangDocContext &CDCtx) {
  375. switch (I->IT) {
  376. case InfoType::IT_namespace:
  377. genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS);
  378. break;
  379. case InfoType::IT_record:
  380. genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS);
  381. break;
  382. case InfoType::IT_enum:
  383. genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS);
  384. break;
  385. case InfoType::IT_function:
  386. genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS);
  387. break;
  388. case InfoType::IT_typedef:
  389. genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS);
  390. break;
  391. case InfoType::IT_default:
  392. return createStringError(llvm::inconvertibleErrorCode(),
  393. "unexpected InfoType");
  394. }
  395. return llvm::Error::success();
  396. }
  397. llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) {
  398. // Write an all_files.md
  399. auto Err = serializeIndex(CDCtx);
  400. if (Err)
  401. return Err;
  402. // Generate the index page.
  403. Err = genIndex(CDCtx);
  404. if (Err)
  405. return Err;
  406. return llvm::Error::success();
  407. }
  408. static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format,
  409. "Generator for MD output.");
  410. // This anchor is used to force the linker to link in the generated object
  411. // file and thus register the generator.
  412. volatile int MDGeneratorAnchorSource = 0;
  413. } // namespace doc
  414. } // namespace clang