Skip to content

Commit 45499f3

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 6ca9676 commit 45499f3

File tree

9 files changed

+247
-65
lines changed

9 files changed

+247
-65
lines changed

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

Lines changed: 11 additions & 9 deletions
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

Lines changed: 144 additions & 25 deletions
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,19 @@ 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, llvm::sys::path::Style::posix,
122+
R.getFileBaseName() + ".md");
123+
// Paths in markdown use posix separators.
124+
llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
125+
OS << "[" << R.Name << "](" << Path << ")";
126+
}
127+
128+
static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I,
129+
llvm::raw_ostream &OS) {
108130
if (I.Scoped)
109131
writeLine("| enum class " + I.Name + " |", OS);
110132
else
@@ -118,13 +140,14 @@ static void genMarkdown(const EnumInfo &I, llvm::raw_ostream &OS) {
118140
Members << "| " << N << " |\n";
119141
writeLine(Members.str(), OS);
120142
if (I.DefLoc)
121-
writeFileDefinition(I.DefLoc.getValue(), OS);
143+
writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
122144

123145
for (const auto &C : I.Description)
124146
writeDescription(C, OS);
125147
}
126148

127-
static void genMarkdown(const FunctionInfo &I, llvm::raw_ostream &OS) {
149+
static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
150+
llvm::raw_ostream &OS) {
128151
std::string Buffer;
129152
llvm::raw_string_ostream Stream(Buffer);
130153
bool First = true;
@@ -145,13 +168,14 @@ static void genMarkdown(const FunctionInfo &I, llvm::raw_ostream &OS) {
145168
Stream.str() + ")"),
146169
OS);
147170
if (I.DefLoc)
148-
writeFileDefinition(I.DefLoc.getValue(), OS);
171+
writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
149172

150173
for (const auto &C : I.Description)
151174
writeDescription(C, OS);
152175
}
153176

154-
static void genMarkdown(const NamespaceInfo &I, llvm::raw_ostream &OS) {
177+
static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I,
178+
llvm::raw_ostream &OS) {
155179
if (I.Name == "")
156180
writeHeader("Global Namespace", 1, OS);
157181
else
@@ -164,36 +188,47 @@ static void genMarkdown(const NamespaceInfo &I, llvm::raw_ostream &OS) {
164188
writeNewLine(OS);
165189
}
166190

191+
llvm::SmallString<64> BasePath = I.getRelativeFilePath("");
192+
167193
if (!I.ChildNamespaces.empty()) {
168194
writeHeader("Namespaces", 2, OS);
169-
for (const auto &R : I.ChildNamespaces)
170-
writeLine(R.Name, OS);
195+
for (const auto &R : I.ChildNamespaces) {
196+
OS << "* ";
197+
writeNameLink(BasePath, R, OS);
198+
OS << "\n";
199+
}
171200
writeNewLine(OS);
172201
}
202+
173203
if (!I.ChildRecords.empty()) {
174204
writeHeader("Records", 2, OS);
175-
for (const auto &R : I.ChildRecords)
176-
writeLine(R.Name, OS);
205+
for (const auto &R : I.ChildRecords) {
206+
OS << "* ";
207+
writeNameLink(BasePath, R, OS);
208+
OS << "\n";
209+
}
177210
writeNewLine(OS);
178211
}
212+
179213
if (!I.ChildFunctions.empty()) {
180214
writeHeader("Functions", 2, OS);
181215
for (const auto &F : I.ChildFunctions)
182-
genMarkdown(F, OS);
216+
genMarkdown(CDCtx, F, OS);
183217
writeNewLine(OS);
184218
}
185219
if (!I.ChildEnums.empty()) {
186220
writeHeader("Enums", 2, OS);
187221
for (const auto &E : I.ChildEnums)
188-
genMarkdown(E, OS);
222+
genMarkdown(CDCtx, E, OS);
189223
writeNewLine(OS);
190224
}
191225
}
192226

193-
static void genMarkdown(const RecordInfo &I, llvm::raw_ostream &OS) {
227+
static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
228+
llvm::raw_ostream &OS) {
194229
writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
195230
if (I.DefLoc)
196-
writeFileDefinition(I.DefLoc.getValue(), OS);
231+
writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
197232

198233
if (!I.Description.empty()) {
199234
for (const auto &C : I.Description)
@@ -234,24 +269,94 @@ static void genMarkdown(const RecordInfo &I, llvm::raw_ostream &OS) {
234269
if (!I.ChildFunctions.empty()) {
235270
writeHeader("Functions", 2, OS);
236271
for (const auto &F : I.ChildFunctions)
237-
genMarkdown(F, OS);
272+
genMarkdown(CDCtx, F, OS);
238273
writeNewLine(OS);
239274
}
240275
if (!I.ChildEnums.empty()) {
241276
writeHeader("Enums", 2, OS);
242277
for (const auto &E : I.ChildEnums)
243-
genMarkdown(E, OS);
278+
genMarkdown(CDCtx, E, OS);
244279
writeNewLine(OS);
245280
}
246281
}
247282

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

253357
llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
254358
const ClangDocContext &CDCtx) override;
359+
llvm::Error createResources(ClangDocContext &CDCtx) override;
255360
};
256361

257362
const char *MDGenerator::Format = "md";
@@ -260,16 +365,16 @@ llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
260365
const ClangDocContext &CDCtx) {
261366
switch (I->IT) {
262367
case InfoType::IT_namespace:
263-
genMarkdown(*static_cast<clang::doc::NamespaceInfo *>(I), OS);
368+
genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS);
264369
break;
265370
case InfoType::IT_record:
266-
genMarkdown(*static_cast<clang::doc::RecordInfo *>(I), OS);
371+
genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS);
267372
break;
268373
case InfoType::IT_enum:
269-
genMarkdown(*static_cast<clang::doc::EnumInfo *>(I), OS);
374+
genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS);
270375
break;
271376
case InfoType::IT_function:
272-
genMarkdown(*static_cast<clang::doc::FunctionInfo *>(I), OS);
377+
genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS);
273378
break;
274379
case InfoType::IT_default:
275380
return createStringError(llvm::inconvertibleErrorCode(),
@@ -278,11 +383,25 @@ llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
278383
return llvm::Error::success();
279384
}
280385

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

284-
// This anchor is used to force the linker to link in the generated object file
285-
// and thus register the generator.
403+
// This anchor is used to force the linker to link in the generated object
404+
// file and thus register the generator.
286405
volatile int MDGeneratorAnchorSource = 0;
287406

288407
} // namespace doc

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

Lines changed: 46 additions & 0 deletions
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)