| //===-- Generators.cpp - Generator Registry ----------------------*- C++-*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "Generators.h" |
| #include "support/File.h" |
| #include "llvm/Support/TimeProfiler.h" |
| |
| LLVM_INSTANTIATE_REGISTRY(clang::doc::GeneratorRegistry) |
| |
| using namespace llvm; |
| using namespace llvm::json; |
| using namespace llvm::mustache; |
| |
| namespace clang { |
| namespace doc { |
| |
| llvm::Expected<std::unique_ptr<Generator>> |
| findGeneratorByName(llvm::StringRef Format) { |
| for (const auto &Generator : GeneratorRegistry::entries()) { |
| if (Generator.getName() != Format) |
| continue; |
| return Generator.instantiate(); |
| } |
| return createStringError(llvm::inconvertibleErrorCode(), |
| "can't find generator: " + Format); |
| } |
| |
| // Enum conversion |
| |
| std::string getTagType(TagTypeKind AS) { |
| switch (AS) { |
| case TagTypeKind::Class: |
| return "class"; |
| case TagTypeKind::Union: |
| return "union"; |
| case TagTypeKind::Interface: |
| return "interface"; |
| case TagTypeKind::Struct: |
| return "struct"; |
| case TagTypeKind::Enum: |
| return "enum"; |
| } |
| llvm_unreachable("Unknown TagTypeKind"); |
| } |
| |
| Error createFileOpenError(StringRef FileName, std::error_code EC) { |
| return createFileError("cannot open file " + FileName, EC); |
| } |
| |
| Error MustacheGenerator::setupTemplate( |
| std::unique_ptr<MustacheTemplateFile> &Template, StringRef TemplatePath, |
| std::vector<std::pair<StringRef, StringRef>> Partials) { |
| auto T = MustacheTemplateFile::createMustacheFile(TemplatePath); |
| if (Error Err = T.takeError()) |
| return Err; |
| Template = std::move(T.get()); |
| for (const auto &[Name, FileName] : Partials) |
| if (auto Err = Template->registerPartialFile(Name, FileName)) |
| return Err; |
| return Error::success(); |
| } |
| |
| Error MustacheGenerator::generateDocumentation( |
| StringRef RootDir, StringMap<std::unique_ptr<doc::Info>> Infos, |
| const clang::doc::ClangDocContext &CDCtx, std::string DirName) { |
| { |
| llvm::TimeTraceScope TS("Setup Templates"); |
| if (auto Err = setupTemplateFiles(CDCtx)) |
| return Err; |
| } |
| |
| { |
| llvm::TimeTraceScope TS("Generate JSON for Mustache"); |
| if (auto JSONGenerator = findGeneratorByName("json")) { |
| if (Error Err = JSONGenerator.get()->generateDocumentation( |
| RootDir, std::move(Infos), CDCtx)) |
| return Err; |
| } else |
| return JSONGenerator.takeError(); |
| } |
| |
| SmallString<128> JSONDirPath(RootDir); |
| SmallString<128> DocsDirPath(RootDir); |
| { |
| TimeTraceScope TS("Create Output Directories"); |
| sys::path::append(JSONDirPath, "json"); |
| if (auto EC = sys::fs::create_directories(JSONDirPath)) |
| return createFileError(JSONDirPath, EC); |
| sys::path::append(DocsDirPath, DirName); |
| if (auto EC = sys::fs::create_directories(DocsDirPath)) |
| return createFileError(DocsDirPath, EC); |
| } |
| |
| { |
| llvm::TimeTraceScope TS("Iterate JSON files"); |
| std::error_code EC; |
| sys::fs::recursive_directory_iterator JSONIter(JSONDirPath, EC); |
| std::vector<json::Value> JSONFiles; |
| JSONFiles.reserve(Infos.size()); |
| if (EC) |
| return createStringError("Failed to create directory iterator."); |
| |
| while (JSONIter != sys::fs::recursive_directory_iterator()) { |
| // create the same directory structure in the docs format dir |
| if (JSONIter->type() == sys::fs::file_type::directory_file) { |
| SmallString<128> DocsClonedPath(JSONIter->path()); |
| sys::path::replace_path_prefix(DocsClonedPath, JSONDirPath, |
| DocsDirPath); |
| if (auto EC = sys::fs::create_directories(DocsClonedPath)) { |
| return createFileError(DocsClonedPath, EC); |
| } |
| } |
| |
| if (EC) |
| return createFileError("Failed to iterate: " + JSONIter->path(), EC); |
| |
| auto Path = StringRef(JSONIter->path()); |
| if (!Path.ends_with(".json")) { |
| JSONIter.increment(EC); |
| continue; |
| } |
| |
| auto File = MemoryBuffer::getFile(Path); |
| if (EC = File.getError(); EC) { |
| unsigned ID = CDCtx.Diags.getCustomDiagID(DiagnosticsEngine::Warning, |
| "Failed to open file: %0 %1"); |
| CDCtx.Diags.Report(ID) << Path << EC.message(); |
| JSONIter.increment(EC); |
| continue; |
| } |
| |
| auto Parsed = json::parse((*File)->getBuffer()); |
| if (!Parsed) |
| return Parsed.takeError(); |
| auto ValidJSON = Parsed.get(); |
| |
| std::error_code FileErr; |
| SmallString<128> DocsFilePath(JSONIter->path()); |
| sys::path::replace_path_prefix(DocsFilePath, JSONDirPath, DocsDirPath); |
| sys::path::replace_extension(DocsFilePath, DirName); |
| raw_fd_ostream InfoOS(DocsFilePath, FileErr, sys::fs::OF_None); |
| if (FileErr) |
| return createFileOpenError(Path, FileErr); |
| |
| auto RelativeRootPath = getRelativePathToRoot(DocsFilePath, DocsDirPath); |
| auto InfoTypeStr = |
| getInfoTypeStr(Parsed->getAsObject(), sys::path::stem(DocsFilePath)); |
| if (!InfoTypeStr) |
| return InfoTypeStr.takeError(); |
| if (Error Err = generateDocForJSON(*Parsed, InfoOS, CDCtx, |
| InfoTypeStr.get(), RelativeRootPath)) |
| return Err; |
| JSONIter.increment(EC); |
| } |
| } |
| |
| return Error::success(); |
| } |
| |
| Expected<std::string> MustacheGenerator::getInfoTypeStr(Object *Info, |
| StringRef Filename) { |
| // Checking for a USR ensures that only the special top-level index file is |
| // caught here, since it is not an Info. |
| if (Filename == "index" && !Info->get("USR")) |
| return "index"; |
| auto StrValue = (*Info)["InfoType"]; |
| if (StrValue.kind() != json::Value::Kind::String) |
| return createStringError("JSON file '%s' does not contain key: 'InfoType'.", |
| 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()); |
| return ObjTypeStr.value().str(); |
| } |
| |
| SmallString<128> |
| MustacheGenerator::getRelativePathToRoot(StringRef PathToFile, |
| StringRef DocsRootPath) { |
| SmallString<128> PathVec(PathToFile); |
| // Remove filename, or else the relative path will have an extra "../" |
| sys::path::remove_filename(PathVec); |
| return computeRelativePath(DocsRootPath, PathVec); |
| } |
| |
| llvm::Error Generator::createResources(ClangDocContext &CDCtx) { |
| return llvm::Error::success(); |
| } |
| |
| // A function to add a reference to Info in Idx. |
| // Given an Info X with the following namespaces: [B,A]; a reference to X will |
| // be added in the children of a reference to B, which should be also a child of |
| // a reference to A, where A is a child of Idx. |
| // Idx |
| // |-- A |
| // |--B |
| // |--X |
| // If the references to the namespaces do not exist, they will be created. If |
| // the references already exist, the same one will be used. |
| void Generator::addInfoToIndex(Index &Idx, const doc::Info *Info) { |
| // Index pointer that will be moving through Idx until the first parent |
| // namespace of Info (where the reference has to be inserted) is found. |
| Index *I = &Idx; |
| // The Namespace vector includes the upper-most namespace at the end so the |
| // loop will start from the end to find each of the namespaces. |
| for (const auto &R : llvm::reverse(Info->Namespace)) { |
| // Look for the current namespace in the children of the index I is |
| // pointing. |
| auto It = llvm::find(I->Children, R.USR); |
| if (It != I->Children.end()) { |
| // If it is found, just change I to point the namespace reference found. |
| I = &*It; |
| } else { |
| // If it is not found a new reference is created |
| I->Children.emplace_back(R.USR, R.Name, R.RefType, R.Path); |
| // I is updated with the reference of the new namespace reference |
| I = &I->Children.back(); |
| } |
| } |
| // Look for Info in the vector where it is supposed to be; it could already |
| // exist if it is a parent namespace of an Info already passed to this |
| // function. |
| auto It = llvm::find(I->Children, Info->USR); |
| if (It == I->Children.end()) { |
| // If it is not in the vector it is inserted |
| I->Children.emplace_back(Info->USR, Info->extractName(), Info->IT, |
| Info->Path); |
| } else { |
| // If it not in the vector we only check if Path and Name are not empty |
| // because if the Info was included by a namespace it may not have those |
| // values. |
| if (It->Path.empty()) |
| It->Path = Info->Path; |
| if (It->Name.empty()) |
| It->Name = Info->extractName(); |
| } |
| } |
| |
| // This anchor is used to force the linker to link in the generated object file |
| // and thus register the generators. |
| [[maybe_unused]] static int YAMLGeneratorAnchorDest = YAMLGeneratorAnchorSource; |
| [[maybe_unused]] static int MDGeneratorAnchorDest = MDGeneratorAnchorSource; |
| [[maybe_unused]] static int HTMLGeneratorAnchorDest = HTMLGeneratorAnchorSource; |
| [[maybe_unused]] static int JSONGeneratorAnchorDest = JSONGeneratorAnchorSource; |
| } // namespace doc |
| } // namespace clang |