Skip to content

Commit 0fbaf3a

Browse files
committed
[clang-doc] Improving Markdown Output
This change has two components. The moves the generated file for a namespace to the directory named after the namespace in a file named 'index.<format>'. This greatly improves the browsing experience since the index page is shown by default for a directory. The second improves the markdown output by adding the links to the referenced pages for children objects and the link back to the source code. Patch By: Clayton Differential Revision: https://reviews.llvm.org/D72954
1 parent 0da755d commit 0fbaf3a

File tree

6 files changed

+221
-40
lines changed

6 files changed

+221
-40
lines changed

clang-tools-extra/clang-doc/HTMLGenerator.cpp

+11-9
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,9 @@ genReference(const Reference &Type, StringRef CurrentDirectory,
314314
else
315315
return genLink(Type.Name, "#" + JumpToSection.getValue());
316316
}
317-
llvm::SmallString<128> Path =
318-
computeRelativePath(Type.Path, CurrentDirectory);
319-
llvm::sys::path::append(Path, Type.Name + ".html");
317+
llvm::SmallString<64> Path = Type.getRelativeFilePath(CurrentDirectory);
318+
llvm::sys::path::append(Path, Type.getFileBaseName() + ".html");
319+
320320
// Paths in HTML must be in posix-style
321321
llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
322322
if (JumpToSection)
@@ -730,15 +730,17 @@ genHTML(const NamespaceInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx,
730730
if (!I.Description.empty())
731731
Out.emplace_back(genHTML(I.Description));
732732

733+
llvm::SmallString<64> BasePath = I.getRelativeFilePath("");
734+
733735
std::vector<std::unique_ptr<TagNode>> ChildNamespaces =
734-
genReferencesBlock(I.ChildNamespaces, "Namespaces", I.Path);
736+
genReferencesBlock(I.ChildNamespaces, "Namespaces", BasePath);
735737
AppendVector(std::move(ChildNamespaces), Out);
736738
std::vector<std::unique_ptr<TagNode>> ChildRecords =
737-
genReferencesBlock(I.ChildRecords, "Records", I.Path);
739+
genReferencesBlock(I.ChildRecords, "Records", BasePath);
738740
AppendVector(std::move(ChildRecords), Out);
739741

740742
std::vector<std::unique_ptr<TagNode>> ChildFunctions =
741-
genFunctionsBlock(I.ChildFunctions, CDCtx, I.Path);
743+
genFunctionsBlock(I.ChildFunctions, CDCtx, BasePath);
742744
AppendVector(std::move(ChildFunctions), Out);
743745
std::vector<std::unique_ptr<TagNode>> ChildEnums =
744746
genEnumsBlock(I.ChildEnums, CDCtx);
@@ -860,8 +862,8 @@ llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
860862
"unexpected info type");
861863
}
862864

863-
HTMLFile F =
864-
genInfoFile(InfoTitle, I->Path, MainContentNodes, InfoIndex, CDCtx);
865+
HTMLFile F = genInfoFile(InfoTitle, I->getRelativeFilePath(""),
866+
MainContentNodes, InfoIndex, CDCtx);
865867
F.Render(OS);
866868

867869
return llvm::Error::success();
@@ -902,7 +904,7 @@ static llvm::Error SerializeIndex(ClangDocContext &CDCtx) {
902904
J.attribute("USR", toHex(llvm::toStringRef(I.USR)));
903905
J.attribute("Name", I.Name);
904906
J.attribute("RefType", getRefType(I.RefType));
905-
J.attribute("Path", I.Path);
907+
J.attribute("Path", I.getRelativeFilePath(""));
906908
J.attributeArray("Children", [&] {
907909
for (const Index &C : I.Children)
908910
IndexToJSON(C);

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

+141-25
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,20 @@ static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
5050
OS << std::string(Num, '#') + " " + Text << "\n\n";
5151
}
5252

53-
static void writeFileDefinition(const Location &L, raw_ostream &OS) {
54-
OS << genItalic("Defined at line " + std::to_string(L.LineNumber) + " of " +
55-
L.Filename)
56-
<< "\n\n";
53+
static void writeFileDefinition(const ClangDocContext &CDCtx, const Location &L,
54+
raw_ostream &OS) {
55+
56+
if (!CDCtx.RepositoryUrl) {
57+
OS << "*Defined at " << L.Filename << "#" << std::to_string(L.LineNumber)
58+
<< "*";
59+
} else {
60+
OS << "*Defined at [" << L.Filename << "#" << std::to_string(L.LineNumber)
61+
<< "](" << StringRef{CDCtx.RepositoryUrl.getValue()}
62+
<< llvm::sys::path::relative_path(L.Filename) << "#"
63+
<< std::to_string(L.LineNumber) << ")"
64+
<< "*";
65+
}
66+
OS << "\n\n";
5767
}
5868

5969
static void writeDescription(const CommentInfo &I, raw_ostream &OS) {
@@ -104,7 +114,16 @@ static void writeDescription(const CommentInfo &I, raw_ostream &OS) {
104114
}
105115
}
106116

107-
static void genMarkdown(const EnumInfo &I, llvm::raw_ostream &OS) {
117+
static void writeNameLink(const StringRef &CurrentPath, const Reference &R,
118+
llvm::raw_ostream &OS) {
119+
120+
llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath);
121+
llvm::sys::path::append(Path, R.getFileBaseName() + ".md");
122+
OS << "[" << R.Name << "](" << Path << ")";
123+
}
124+
125+
static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I,
126+
llvm::raw_ostream &OS) {
108127
if (I.Scoped)
109128
writeLine("| enum class " + I.Name + " |", OS);
110129
else
@@ -118,13 +137,14 @@ static void genMarkdown(const EnumInfo &I, llvm::raw_ostream &OS) {
118137
Members << "| " << N << " |\n";
119138
writeLine(Members.str(), OS);
120139
if (I.DefLoc)
121-
writeFileDefinition(I.DefLoc.getValue(), OS);
140+
writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
122141

123142
for (const auto &C : I.Description)
124143
writeDescription(C, OS);
125144
}
126145

127-
static void genMarkdown(const FunctionInfo &I, llvm::raw_ostream &OS) {
146+
static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
147+
llvm::raw_ostream &OS) {
128148
std::string Buffer;
129149
llvm::raw_string_ostream Stream(Buffer);
130150
bool First = true;
@@ -145,13 +165,14 @@ static void genMarkdown(const FunctionInfo &I, llvm::raw_ostream &OS) {
145165
Stream.str() + ")"),
146166
OS);
147167
if (I.DefLoc)
148-
writeFileDefinition(I.DefLoc.getValue(), OS);
168+
writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
149169

150170
for (const auto &C : I.Description)
151171
writeDescription(C, OS);
152172
}
153173

154-
static void genMarkdown(const NamespaceInfo &I, llvm::raw_ostream &OS) {
174+
static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I,
175+
llvm::raw_ostream &OS) {
155176
if (I.Name == "")
156177
writeHeader("Global Namespace", 1, OS);
157178
else
@@ -164,36 +185,47 @@ static void genMarkdown(const NamespaceInfo &I, llvm::raw_ostream &OS) {
164185
writeNewLine(OS);
165186
}
166187

188+
llvm::SmallString<64> BasePath = I.getRelativeFilePath("");
189+
167190
if (!I.ChildNamespaces.empty()) {
168191
writeHeader("Namespaces", 2, OS);
169-
for (const auto &R : I.ChildNamespaces)
170-
writeLine(R.Name, OS);
192+
for (const auto &R : I.ChildNamespaces) {
193+
OS << "* ";
194+
writeNameLink(BasePath, R, OS);
195+
OS << "\n";
196+
}
171197
writeNewLine(OS);
172198
}
199+
173200
if (!I.ChildRecords.empty()) {
174201
writeHeader("Records", 2, OS);
175-
for (const auto &R : I.ChildRecords)
176-
writeLine(R.Name, OS);
202+
for (const auto &R : I.ChildRecords) {
203+
OS << "* ";
204+
writeNameLink(BasePath, R, OS);
205+
OS << "\n";
206+
}
177207
writeNewLine(OS);
178208
}
209+
179210
if (!I.ChildFunctions.empty()) {
180211
writeHeader("Functions", 2, OS);
181212
for (const auto &F : I.ChildFunctions)
182-
genMarkdown(F, OS);
213+
genMarkdown(CDCtx, F, OS);
183214
writeNewLine(OS);
184215
}
185216
if (!I.ChildEnums.empty()) {
186217
writeHeader("Enums", 2, OS);
187218
for (const auto &E : I.ChildEnums)
188-
genMarkdown(E, OS);
219+
genMarkdown(CDCtx, E, OS);
189220
writeNewLine(OS);
190221
}
191222
}
192223

193-
static void genMarkdown(const RecordInfo &I, llvm::raw_ostream &OS) {
224+
static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
225+
llvm::raw_ostream &OS) {
194226
writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
195227
if (I.DefLoc)
196-
writeFileDefinition(I.DefLoc.getValue(), OS);
228+
writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
197229

198230
if (!I.Description.empty()) {
199231
for (const auto &C : I.Description)
@@ -234,24 +266,94 @@ static void genMarkdown(const RecordInfo &I, llvm::raw_ostream &OS) {
234266
if (!I.ChildFunctions.empty()) {
235267
writeHeader("Functions", 2, OS);
236268
for (const auto &F : I.ChildFunctions)
237-
genMarkdown(F, OS);
269+
genMarkdown(CDCtx, F, OS);
238270
writeNewLine(OS);
239271
}
240272
if (!I.ChildEnums.empty()) {
241273
writeHeader("Enums", 2, OS);
242274
for (const auto &E : I.ChildEnums)
243-
genMarkdown(E, OS);
275+
genMarkdown(CDCtx, E, OS);
244276
writeNewLine(OS);
245277
}
246278
}
247279

280+
static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) {
281+
// Write out the heading level starting at ##
282+
OS << "##" << std::string(Level, '#') << " ";
283+
writeNameLink("", I, OS);
284+
OS << "\n";
285+
}
286+
287+
static llvm::Error serializeIndex(ClangDocContext &CDCtx) {
288+
std::error_code FileErr;
289+
llvm::SmallString<128> FilePath;
290+
llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
291+
llvm::sys::path::append(FilePath, "all_files.md");
292+
llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
293+
if (FileErr)
294+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
295+
"error creating index file: " +
296+
FileErr.message());
297+
298+
CDCtx.Idx.sort();
299+
OS << "# All Files";
300+
if (!CDCtx.ProjectName.empty())
301+
OS << " for " << CDCtx.ProjectName;
302+
OS << "\n\n";
303+
304+
for (auto C : CDCtx.Idx.Children)
305+
serializeReference(OS, C, 0);
306+
307+
return llvm::Error::success();
308+
}
309+
310+
static llvm::Error genIndex(ClangDocContext &CDCtx) {
311+
std::error_code FileErr;
312+
llvm::SmallString<128> FilePath;
313+
llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
314+
llvm::sys::path::append(FilePath, "index.md");
315+
llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
316+
if (FileErr)
317+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
318+
"error creating index file: " +
319+
FileErr.message());
320+
CDCtx.Idx.sort();
321+
OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n";
322+
for (auto C : CDCtx.Idx.Children) {
323+
if (!C.Children.empty()) {
324+
const char *Type;
325+
switch (C.RefType) {
326+
case InfoType::IT_namespace:
327+
Type = "Namespace";
328+
break;
329+
case InfoType::IT_record:
330+
Type = "Type";
331+
break;
332+
case InfoType::IT_enum:
333+
Type = "Enum";
334+
break;
335+
case InfoType::IT_function:
336+
Type = "Function";
337+
break;
338+
case InfoType::IT_default:
339+
Type = "Other";
340+
}
341+
OS << "* " << Type << ": [" << C.Name << "](";
342+
if (!C.Path.empty())
343+
OS << C.Path << "/";
344+
OS << C.Name << ")\n";
345+
}
346+
}
347+
return llvm::Error::success();
348+
}
248349
/// Generator for Markdown documentation.
249350
class MDGenerator : public Generator {
250351
public:
251352
static const char *Format;
252353

253354
llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
254355
const ClangDocContext &CDCtx) override;
356+
llvm::Error createResources(ClangDocContext &CDCtx) override;
255357
};
256358

257359
const char *MDGenerator::Format = "md";
@@ -260,16 +362,16 @@ llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
260362
const ClangDocContext &CDCtx) {
261363
switch (I->IT) {
262364
case InfoType::IT_namespace:
263-
genMarkdown(*static_cast<clang::doc::NamespaceInfo *>(I), OS);
365+
genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS);
264366
break;
265367
case InfoType::IT_record:
266-
genMarkdown(*static_cast<clang::doc::RecordInfo *>(I), OS);
368+
genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS);
267369
break;
268370
case InfoType::IT_enum:
269-
genMarkdown(*static_cast<clang::doc::EnumInfo *>(I), OS);
371+
genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS);
270372
break;
271373
case InfoType::IT_function:
272-
genMarkdown(*static_cast<clang::doc::FunctionInfo *>(I), OS);
374+
genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS);
273375
break;
274376
case InfoType::IT_default:
275377
return createStringError(llvm::inconvertibleErrorCode(),
@@ -278,11 +380,25 @@ llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
278380
return llvm::Error::success();
279381
}
280382

383+
llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) {
384+
// Write an all_files.md
385+
auto Err = serializeIndex(CDCtx);
386+
if (Err)
387+
return Err;
388+
389+
// Generate the index page.
390+
Err = genIndex(CDCtx);
391+
if (Err)
392+
return Err;
393+
394+
return llvm::Error::success();
395+
}
396+
281397
static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format,
282398
"Generator for MD output.");
283399

284-
// This anchor is used to force the linker to link in the generated object file
285-
// and thus register the generator.
400+
// This anchor is used to force the linker to link in the generated object
401+
// file and thus register the generator.
286402
volatile int MDGeneratorAnchorSource = 0;
287403

288404
} // namespace doc

clang-tools-extra/clang-doc/Representation.cpp

+46
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,52 @@ mergeInfos(std::vector<std::unique_ptr<Info>> &Values) {
114114
}
115115
}
116116

117+
static llvm::SmallString<64>
118+
calculateRelativeFilePath(const InfoType &Type, const StringRef &Path,
119+
const StringRef &Name, const StringRef &CurrentPath) {
120+
llvm::SmallString<64> FilePath;
121+
122+
if (CurrentPath != Path) {
123+
// iterate back to the top
124+
for (llvm::sys::path::const_iterator I =
125+
llvm::sys::path::begin(CurrentPath);
126+
I != llvm::sys::path::end(CurrentPath); ++I)
127+
llvm::sys::path::append(FilePath, "..");
128+
llvm::sys::path::append(FilePath, Path);
129+
}
130+
131+
// Namespace references have a Path to the parent namespace, but
132+
// the file is actually in the subdirectory for the namespace.
133+
if (Type == doc::InfoType::IT_namespace)
134+
llvm::sys::path::append(FilePath, Name);
135+
136+
return llvm::sys::path::relative_path(FilePath);
137+
}
138+
139+
llvm::SmallString<64>
140+
Reference::getRelativeFilePath(const StringRef &CurrentPath) const {
141+
return calculateRelativeFilePath(RefType, Path, Name, CurrentPath);
142+
}
143+
144+
llvm::SmallString<16> Reference::getFileBaseName() const {
145+
if (RefType == InfoType::IT_namespace)
146+
return llvm::SmallString<16>("index");
147+
148+
return Name;
149+
}
150+
151+
llvm::SmallString<64>
152+
Info::getRelativeFilePath(const StringRef &CurrentPath) const {
153+
return calculateRelativeFilePath(IT, Path, extractName(), CurrentPath);
154+
}
155+
156+
llvm::SmallString<16> Info::getFileBaseName() const {
157+
if (IT == InfoType::IT_namespace)
158+
return llvm::SmallString<16>("index");
159+
160+
return extractName();
161+
}
162+
117163
bool Reference::mergeable(const Reference &Other) {
118164
return RefType == Other.RefType && USR == Other.USR;
119165
}

0 commit comments

Comments
 (0)