llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang-tools-extra Author: Erick Velez (evelez7) <details> <summary>Changes</summary> 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. --- 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: - (modified) clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp (+78-409) - (modified) clang-tools-extra/clang-doc/assets/class-template.mustache (+32-30) - (modified) clang-tools-extra/clang-doc/assets/enum-template.mustache (+9-3) - (modified) clang-tools-extra/clang-doc/assets/function-template.mustache (+1-1) - (modified) clang-tools-extra/clang-doc/assets/namespace-template.mustache (+25-20) - (modified) clang-tools-extra/test/clang-doc/basic-project.mustache.test (+13-57) - (modified) clang-tools-extra/test/clang-doc/mustache-index.cpp (+9-5) - (modified) clang-tools-extra/test/clang-doc/mustache-separate-namespace.cpp (+6-2) - (modified) clang-tools-extra/unittests/clang-doc/HTMLMustacheGeneratorTest.cpp (-129) ``````````diff 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] `````````` </details> https://github.com/llvm/llvm-project/pull/149589 _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits