-
Notifications
You must be signed in to change notification settings - Fork 14.5k
[clang-doc] integrate JSON as the source for Mustache templates #149589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: users/evelez7/clang-doc-refactor-json-for-mustache
Are you sure you want to change the base?
[clang-doc] integrate JSON as the source for Mustache templates #149589
Conversation
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
@llvm/pr-subscribers-clang-tools-extra Author: Erick Velez (evelez7) ChangesThis patch integrates JSON as the source to generate HTML Mustache templates. The Mustache generator calls the JSON generator and reads JSON files on the disk to produce HTML serially. Patch is 48.74 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/149589.diff 9 Files Affected:
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index 7aeaa1b7cf67d..98e2935a8aada 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -27,6 +27,9 @@ using namespace llvm::mustache;
namespace clang {
namespace doc {
+static Error generateDocForJSON(json::Value &JSON, StringRef Filename,
+ StringRef Path, raw_fd_ostream &OS,
+ const ClangDocContext &CDCtx);
static Error createFileOpenError(StringRef FileName, std::error_code EC) {
return createFileError("cannot open file " + FileName, EC);
@@ -132,404 +135,65 @@ Error MustacheHTMLGenerator::generateDocs(
return Err;
}
- // Track which directories we already tried to create.
- StringSet<> CreatedDirs;
- // Collect all output by file name and create the necessary directories.
- StringMap<std::vector<doc::Info *>> FileToInfos;
- for (const auto &Group : Infos) {
- llvm::TimeTraceScope TS("setup directories");
- doc::Info *Info = Group.getValue().get();
-
- SmallString<128> Path;
- sys::path::native(RootDir, Path);
- sys::path::append(Path, Info->getRelativeFilePath(""));
- if (!CreatedDirs.contains(Path)) {
- if (std::error_code EC = sys::fs::create_directories(Path))
- return createStringError(EC, "failed to create directory '%s'.",
- Path.c_str());
- CreatedDirs.insert(Path);
- }
-
- sys::path::append(Path, Info->getFileBaseName() + ".html");
- FileToInfos[Path].push_back(Info);
+ {
+ llvm::TimeTraceScope TS("Generate JSON for Mustache");
+ if (auto JSONGenerator = findGeneratorByName("json")) {
+ if (Error Err = JSONGenerator.get()->generateDocs(
+ RootDir, std::move(Infos), CDCtx))
+ return Err;
+ } else
+ return JSONGenerator.takeError();
}
+ StringMap<json::Value> JSONFileMap;
{
- llvm::TimeTraceScope TS("Generate Docs");
- for (const auto &Group : FileToInfos) {
- llvm::TimeTraceScope TS("Info to Doc");
+ llvm::TimeTraceScope TS("Iterate JSON files");
+ std::error_code EC;
+ sys::fs::directory_iterator JSONIter(RootDir, EC);
+ std::vector<json::Value> JSONFiles;
+ JSONFiles.reserve(Infos.size());
+ if (EC)
+ return createStringError("Failed to create directory iterator.");
+
+ while (JSONIter != sys::fs::directory_iterator()) {
+ if (EC)
+ return createStringError(EC, "Failed to iterate directory");
+
+ auto Path = StringRef(JSONIter->path());
+ if (!Path.ends_with(".json")) {
+ JSONIter.increment(EC);
+ continue;
+ }
+
+ auto File = MemoryBuffer::getFile(Path);
+ if ((EC = File.getError()))
+ continue;
+
+ auto Parsed = json::parse((*File)->getBuffer());
+ if (!Parsed)
+ return Parsed.takeError();
+
std::error_code FileErr;
- raw_fd_ostream InfoOS(Group.getKey(), FileErr, sys::fs::OF_None);
+ SmallString<16> HTMLPath(Path.begin(), Path.end());
+ sys::path::replace_extension(HTMLPath, "html");
+ raw_fd_ostream InfoOS(HTMLPath, FileErr, sys::fs::OF_None);
if (FileErr)
- return createFileOpenError(Group.getKey(), FileErr);
+ return createFileOpenError(Path, FileErr);
- for (const auto &Info : Group.getValue())
- if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx))
- return Err;
+ if (Error Err = generateDocForJSON(*Parsed, sys::path::stem(HTMLPath),
+ HTMLPath, InfoOS, CDCtx))
+ return Err;
+ JSONIter.increment(EC);
}
}
- return Error::success();
-}
-
-static json::Value
-extractValue(const Location &L,
- std::optional<StringRef> RepositoryUrl = std::nullopt) {
- Object Obj = Object();
- // TODO: Consider using both Start/End line numbers to improve location report
- Obj.insert({"LineNumber", L.StartLineNumber});
- Obj.insert({"Filename", L.Filename});
-
- if (!L.IsFileInRootDir || !RepositoryUrl)
- return Obj;
- SmallString<128> FileURL(*RepositoryUrl);
- sys::path::append(FileURL, sys::path::Style::posix, L.Filename);
- FileURL += "#" + std::to_string(L.StartLineNumber);
- Obj.insert({"FileURL", FileURL});
-
- return Obj;
-}
-
-static json::Value extractValue(const Reference &I,
- StringRef CurrentDirectory) {
- SmallString<64> Path = I.getRelativeFilePath(CurrentDirectory);
- sys::path::append(Path, I.getFileBaseName() + ".html");
- sys::path::native(Path, sys::path::Style::posix);
- Object Obj = Object();
- Obj.insert({"Link", Path});
- Obj.insert({"Name", I.Name});
- Obj.insert({"QualName", I.QualName});
- Obj.insert({"ID", toHex(toStringRef(I.USR))});
- return Obj;
-}
-
-static json::Value extractValue(const TypedefInfo &I) {
- // Not Supported
- return nullptr;
-}
-
-static json::Value extractValue(const CommentInfo &I) {
- Object Obj = Object();
-
- json::Value ChildVal = Object();
- Object &Child = *ChildVal.getAsObject();
-
- json::Value ChildArr = Array();
- auto &CARef = *ChildArr.getAsArray();
- CARef.reserve(I.Children.size());
- for (const auto &C : I.Children)
- CARef.emplace_back(extractValue(*C));
-
- switch (I.Kind) {
- case CommentKind::CK_TextComment: {
- Obj.insert({commentKindToString(I.Kind), I.Text});
- return Obj;
- }
-
- case CommentKind::CK_BlockCommandComment: {
- Child.insert({"Command", I.Name});
- Child.insert({"Children", ChildArr});
- Obj.insert({commentKindToString(I.Kind), ChildVal});
- return Obj;
- }
-
- case CommentKind::CK_InlineCommandComment: {
- json::Value ArgsArr = Array();
- auto &ARef = *ArgsArr.getAsArray();
- ARef.reserve(I.Args.size());
- for (const auto &Arg : I.Args)
- ARef.emplace_back(Arg);
- Child.insert({"Command", I.Name});
- Child.insert({"Args", ArgsArr});
- Child.insert({"Children", ChildArr});
- Obj.insert({commentKindToString(I.Kind), ChildVal});
- return Obj;
- }
-
- case CommentKind::CK_ParamCommandComment:
- case CommentKind::CK_TParamCommandComment: {
- Child.insert({"ParamName", I.ParamName});
- Child.insert({"Direction", I.Direction});
- Child.insert({"Explicit", I.Explicit});
- Child.insert({"Children", ChildArr});
- Obj.insert({commentKindToString(I.Kind), ChildVal});
- return Obj;
- }
-
- case CommentKind::CK_VerbatimBlockComment: {
- Child.insert({"Text", I.Text});
- if (!I.CloseName.empty())
- Child.insert({"CloseName", I.CloseName});
- Child.insert({"Children", ChildArr});
- Obj.insert({commentKindToString(I.Kind), ChildVal});
- return Obj;
- }
-
- case CommentKind::CK_VerbatimBlockLineComment:
- case CommentKind::CK_VerbatimLineComment: {
- Child.insert({"Text", I.Text});
- Child.insert({"Children", ChildArr});
- Obj.insert({commentKindToString(I.Kind), ChildVal});
- return Obj;
- }
-
- case CommentKind::CK_HTMLStartTagComment: {
- json::Value AttrKeysArray = json::Array();
- json::Value AttrValuesArray = json::Array();
- auto &KeyArr = *AttrKeysArray.getAsArray();
- auto &ValArr = *AttrValuesArray.getAsArray();
- KeyArr.reserve(I.AttrKeys.size());
- ValArr.reserve(I.AttrValues.size());
- for (const auto &K : I.AttrKeys)
- KeyArr.emplace_back(K);
- for (const auto &V : I.AttrValues)
- ValArr.emplace_back(V);
- Child.insert({"Name", I.Name});
- Child.insert({"SelfClosing", I.SelfClosing});
- Child.insert({"AttrKeys", AttrKeysArray});
- Child.insert({"AttrValues", AttrValuesArray});
- Child.insert({"Children", ChildArr});
- Obj.insert({commentKindToString(I.Kind), ChildVal});
- return Obj;
- }
-
- case CommentKind::CK_HTMLEndTagComment: {
- Child.insert({"Name", I.Name});
- Child.insert({"Children", ChildArr});
- Obj.insert({commentKindToString(I.Kind), ChildVal});
- return Obj;
- }
-
- case CommentKind::CK_FullComment:
- case CommentKind::CK_ParagraphComment: {
- Child.insert({"Children", ChildArr});
- Obj.insert({commentKindToString(I.Kind), ChildVal});
- return Obj;
- }
-
- case CommentKind::CK_Unknown: {
- Obj.insert({commentKindToString(I.Kind), I.Text});
- return Obj;
- }
- }
- llvm_unreachable("Unknown comment kind encountered.");
-}
-
-static void maybeInsertLocation(std::optional<Location> Loc,
- const ClangDocContext &CDCtx, Object &Obj) {
- if (!Loc)
- return;
- Location L = *Loc;
- Obj.insert({"Location", extractValue(L, CDCtx.RepositoryUrl)});
-}
-
-static void extractDescriptionFromInfo(ArrayRef<CommentInfo> Descriptions,
- json::Object &EnumValObj) {
- if (Descriptions.empty())
- return;
- json::Value DescArr = Array();
- json::Array &DescARef = *DescArr.getAsArray();
- DescARef.reserve(Descriptions.size());
- for (const CommentInfo &Child : Descriptions)
- DescARef.emplace_back(extractValue(Child));
- EnumValObj.insert({"EnumValueComments", DescArr});
-}
-
-static json::Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir,
- const ClangDocContext &CDCtx) {
- Object Obj = Object();
- Obj.insert({"Name", I.Name});
- Obj.insert({"ID", toHex(toStringRef(I.USR))});
- Obj.insert({"Access", getAccessSpelling(I.Access).str()});
- Obj.insert({"ReturnType", extractValue(I.ReturnType.Type, ParentInfoDir)});
-
- json::Value ParamArr = Array();
- json::Array &ParamARef = *ParamArr.getAsArray();
- ParamARef.reserve(I.Params.size());
- for (const auto Val : enumerate(I.Params)) {
- json::Value V = Object();
- auto &VRef = *V.getAsObject();
- VRef.insert({"Name", Val.value().Name});
- VRef.insert({"Type", Val.value().Type.Name});
- VRef.insert({"End", Val.index() + 1 == I.Params.size()});
- ParamARef.emplace_back(V);
- }
- Obj.insert({"Params", ParamArr});
- maybeInsertLocation(I.DefLoc, CDCtx, Obj);
- return Obj;
-}
-
-static json::Value extractValue(const EnumInfo &I,
- const ClangDocContext &CDCtx) {
- Object Obj = Object();
- std::string EnumType = I.Scoped ? "enum class " : "enum ";
- EnumType += I.Name;
- bool HasComment = llvm::any_of(
- I.Members, [](const EnumValueInfo &M) { return !M.Description.empty(); });
- Obj.insert({"EnumName", EnumType});
- Obj.insert({"HasComment", HasComment});
- Obj.insert({"ID", toHex(toStringRef(I.USR))});
- json::Value EnumArr = Array();
- json::Array &EnumARef = *EnumArr.getAsArray();
- EnumARef.reserve(I.Members.size());
- for (const EnumValueInfo &M : I.Members) {
- json::Value EnumValue = Object();
- auto &EnumValObj = *EnumValue.getAsObject();
- EnumValObj.insert({"Name", M.Name});
- if (!M.ValueExpr.empty())
- EnumValObj.insert({"ValueExpr", M.ValueExpr});
- else
- EnumValObj.insert({"Value", M.Value});
-
- extractDescriptionFromInfo(M.Description, EnumValObj);
- EnumARef.emplace_back(EnumValue);
- }
- Obj.insert({"EnumValues", EnumArr});
-
- extractDescriptionFromInfo(I.Description, Obj);
- maybeInsertLocation(I.DefLoc, CDCtx, Obj);
-
- return Obj;
-}
-
-static void extractScopeChildren(const ScopeChildren &S, Object &Obj,
- StringRef ParentInfoDir,
- const ClangDocContext &CDCtx) {
- json::Value NamespaceArr = Array();
- json::Array &NamespaceARef = *NamespaceArr.getAsArray();
- NamespaceARef.reserve(S.Namespaces.size());
- for (const Reference &Child : S.Namespaces)
- NamespaceARef.emplace_back(extractValue(Child, ParentInfoDir));
-
- if (!NamespaceARef.empty())
- Obj.insert({"Namespace", Object{{"Links", NamespaceArr}}});
-
- json::Value RecordArr = Array();
- json::Array &RecordARef = *RecordArr.getAsArray();
- RecordARef.reserve(S.Records.size());
- for (const Reference &Child : S.Records)
- RecordARef.emplace_back(extractValue(Child, ParentInfoDir));
-
- if (!RecordARef.empty())
- Obj.insert({"Record", Object{{"Links", RecordArr}}});
-
- json::Value FunctionArr = Array();
- json::Array &FunctionARef = *FunctionArr.getAsArray();
- FunctionARef.reserve(S.Functions.size());
-
- json::Value PublicFunctionArr = Array();
- json::Array &PublicFunctionARef = *PublicFunctionArr.getAsArray();
- PublicFunctionARef.reserve(S.Functions.size());
-
- json::Value ProtectedFunctionArr = Array();
- json::Array &ProtectedFunctionARef = *ProtectedFunctionArr.getAsArray();
- ProtectedFunctionARef.reserve(S.Functions.size());
-
- for (const FunctionInfo &Child : S.Functions) {
- json::Value F = extractValue(Child, ParentInfoDir, CDCtx);
- AccessSpecifier Access = Child.Access;
- if (Access == AccessSpecifier::AS_public)
- PublicFunctionARef.emplace_back(F);
- else if (Access == AccessSpecifier::AS_protected)
- ProtectedFunctionARef.emplace_back(F);
- else
- FunctionARef.emplace_back(F);
- }
-
- if (!FunctionARef.empty())
- Obj.insert({"Function", Object{{"Obj", FunctionArr}}});
-
- if (!PublicFunctionARef.empty())
- Obj.insert({"PublicFunction", Object{{"Obj", PublicFunctionArr}}});
-
- if (!ProtectedFunctionARef.empty())
- Obj.insert({"ProtectedFunction", Object{{"Obj", ProtectedFunctionArr}}});
-
- json::Value EnumArr = Array();
- auto &EnumARef = *EnumArr.getAsArray();
- EnumARef.reserve(S.Enums.size());
- for (const EnumInfo &Child : S.Enums)
- EnumARef.emplace_back(extractValue(Child, CDCtx));
-
- if (!EnumARef.empty())
- Obj.insert({"Enums", Object{{"Obj", EnumArr}}});
-
- json::Value TypedefArr = Array();
- auto &TypedefARef = *TypedefArr.getAsArray();
- TypedefARef.reserve(S.Typedefs.size());
- for (const TypedefInfo &Child : S.Typedefs)
- TypedefARef.emplace_back(extractValue(Child));
-
- if (!TypedefARef.empty())
- Obj.insert({"Typedefs", Object{{"Obj", TypedefArr}}});
-}
-
-static json::Value extractValue(const NamespaceInfo &I,
- const ClangDocContext &CDCtx) {
- Object NamespaceValue = Object();
- std::string InfoTitle = I.Name.empty() ? "Global Namespace"
- : (Twine("namespace ") + I.Name).str();
-
- SmallString<64> BasePath = I.getRelativeFilePath("");
- NamespaceValue.insert({"NamespaceTitle", InfoTitle});
- NamespaceValue.insert({"NamespacePath", BasePath});
-
- extractDescriptionFromInfo(I.Description, NamespaceValue);
- extractScopeChildren(I.Children, NamespaceValue, BasePath, CDCtx);
- return NamespaceValue;
-}
-
-static json::Value extractValue(const RecordInfo &I,
- const ClangDocContext &CDCtx) {
- Object RecordValue = Object();
- extractDescriptionFromInfo(I.Description, RecordValue);
- RecordValue.insert({"Name", I.Name});
- RecordValue.insert({"FullName", I.FullName});
- RecordValue.insert({"RecordType", getTagType(I.TagType)});
-
- maybeInsertLocation(I.DefLoc, CDCtx, RecordValue);
-
- SmallString<64> BasePath = I.getRelativeFilePath("");
- extractScopeChildren(I.Children, RecordValue, BasePath, CDCtx);
- json::Value PublicMembers = Array();
- json::Array &PubMemberRef = *PublicMembers.getAsArray();
- PubMemberRef.reserve(I.Members.size());
- json::Value ProtectedMembers = Array();
- json::Array &ProtMemberRef = *ProtectedMembers.getAsArray();
- ProtMemberRef.reserve(I.Members.size());
- json::Value PrivateMembers = Array();
- json::Array &PrivMemberRef = *PrivateMembers.getAsArray();
- PrivMemberRef.reserve(I.Members.size());
- for (const MemberTypeInfo &Member : I.Members) {
- json::Value MemberValue = Object();
- auto &MVRef = *MemberValue.getAsObject();
- MVRef.insert({"Name", Member.Name});
- MVRef.insert({"Type", Member.Type.Name});
- extractDescriptionFromInfo(Member.Description, MVRef);
-
- if (Member.Access == AccessSpecifier::AS_public)
- PubMemberRef.emplace_back(MemberValue);
- else if (Member.Access == AccessSpecifier::AS_protected)
- ProtMemberRef.emplace_back(MemberValue);
- else if (Member.Access == AccessSpecifier::AS_private)
- ProtMemberRef.emplace_back(MemberValue);
- }
- if (!PubMemberRef.empty())
- RecordValue.insert({"PublicMembers", Object{{"Obj", PublicMembers}}});
- if (!ProtMemberRef.empty())
- RecordValue.insert({"ProtectedMembers", Object{{"Obj", ProtectedMembers}}});
- if (!PrivMemberRef.empty())
- RecordValue.insert({"PrivateMembers", Object{{"Obj", PrivateMembers}}});
-
- return RecordValue;
+ return Error::success();
}
-static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V,
- Info *I) {
+static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V) {
V.getAsObject()->insert({"ProjectName", CDCtx.ProjectName});
json::Value StylesheetArr = Array();
- auto InfoPath = I->getRelativeFilePath("");
- SmallString<128> RelativePath = computeRelativePath("", InfoPath);
+ SmallString<128> RelativePath("./");
sys::path::native(RelativePath, sys::path::Style::posix);
auto *SSA = StylesheetArr.getAsArray();
@@ -555,38 +219,43 @@ static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V,
return Error::success();
}
-Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
- const ClangDocContext &CDCtx) {
- switch (I->IT) {
- case InfoType::IT_namespace: {
- json::Value V =
- extractValue(*static_cast<clang::doc::NamespaceInfo *>(I), CDCtx);
- if (auto Err = setupTemplateValue(CDCtx, V, I))
+static Error generateDocForJSON(json::Value &JSON, StringRef Filename,
+ StringRef Path, raw_fd_ostream &OS,
+ const ClangDocContext &CDCtx) {
+ auto StrValue = (*JSON.getAsObject())["InfoType"];
+ if (StrValue.kind() != json::Value::Kind::String)
+ return createStringError(
+ "JSON file '%s' does not contain 'InfoType' field.",
+ Filename.str().c_str());
+ auto ObjTypeStr = StrValue.getAsString();
+ if (!ObjTypeStr.has_value())
+ return createStringError(
+ "JSON file '%s' does not contain 'InfoType' field as a string.",
+ Filename.str().c_str());
+
+ if (ObjTypeStr.value() == "namespace") {
+ if (auto Err = setupTemplateValue(CDCtx, JSON))
return Err;
assert(NamespaceTemplate && "NamespaceTemplate is nullptr.");
- NamespaceTemplate->render(V, OS);
- break;
- }
- case InfoType::IT_record: {
- json::Value V =
- extractValue(*static_cast<clang::doc::RecordInfo *>(I), CDCtx);
- if (auto Err = setupTemplateValue(CDCtx, V, I))
+ NamespaceTemplate->render(JSON, OS);
+ } else if (ObjTypeStr.value() == "record") {
+ if (auto Err = setupTemplateValue(CDCtx, JSON))
return Err;
- // Serialize the JSON value to the output stream in a readable format.
- RecordTemplate->render(V, OS);
- break;
+ assert(RecordTemplate && "RecordTemplate is nullptr.");
+ RecordTemplate->render(JSON, OS);
}
+ return Error::success();
+}
+
+Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
+ const ClangDocContext &CDCtx) {
+ switch (I->IT) {
case InfoType::IT_enum:
- OS << "IT_enum\n";
- break;
case InfoType::IT_function:
- OS << "IT_Function\n";
- break;
case InfoType::IT_typedef:
- OS << "IT_typedef\n";
- break;
+ case InfoType::IT_namespace:
+ case InfoType::IT_record:
case InfoType::IT_concept:
- break;
case InfoType::IT_variable:
case InfoType::IT_friend:
break;
diff --git a/clang-tools-extra/clang-doc/assets/class-template.mustache b/clang-tools-extra/clang-doc/assets/class-template.mustache
index f9e78f5cd6bc9..a4077323f29e2 100644
--- a/clang-tools-extra/clang-doc/assets/class-template.mustache
+++ b/clang-tools-extra/clang-doc/assets/class-template.mustache
@@ -44,20 +44,20 @@
<main>
<div class="container">
<div class="sidebar">
- <h2>{{RecordType}} {{Name}}</h2>
+ <h2>{{TagType}} {{Name}}</h2>
<ul>
- {{#PublicMembers}}
+ {{#HasPublicMembers}}
<li class="sidebar-section">
- <a class="sidebar-item" href="#PublicMethods">Public Members</a>
+ <a class="sid...
[truncated]
|
This patch integrates JSON as the source to generate HTML Mustache templates. The Mustache generator calls the JSON generator and reads JSON files on the disk to produce HTML serially.