diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -314,9 +314,9 @@ else return genLink(Type.Name, "#" + JumpToSection.getValue()); } - llvm::SmallString<128> Path = - computeRelativePath(Type.Path, CurrentDirectory); - llvm::sys::path::append(Path, Type.Name + ".html"); + llvm::SmallString<64> Path = Type.getRelativeFilePath(CurrentDirectory); + llvm::sys::path::append(Path, Type.getFileBaseName() + ".html"); + // Paths in HTML must be in posix-style llvm::sys::path::native(Path, llvm::sys::path::Style::posix); if (JumpToSection) @@ -730,15 +730,17 @@ if (!I.Description.empty()) Out.emplace_back(genHTML(I.Description)); + llvm::SmallString<64> BasePath = I.getRelativeFilePath(""); + std::vector> ChildNamespaces = - genReferencesBlock(I.ChildNamespaces, "Namespaces", I.Path); + genReferencesBlock(I.ChildNamespaces, "Namespaces", BasePath); AppendVector(std::move(ChildNamespaces), Out); std::vector> ChildRecords = - genReferencesBlock(I.ChildRecords, "Records", I.Path); + genReferencesBlock(I.ChildRecords, "Records", BasePath); AppendVector(std::move(ChildRecords), Out); std::vector> ChildFunctions = - genFunctionsBlock(I.ChildFunctions, CDCtx, I.Path); + genFunctionsBlock(I.ChildFunctions, CDCtx, BasePath); AppendVector(std::move(ChildFunctions), Out); std::vector> ChildEnums = genEnumsBlock(I.ChildEnums, CDCtx); @@ -860,8 +862,8 @@ "unexpected info type"); } - HTMLFile F = - genInfoFile(InfoTitle, I->Path, MainContentNodes, InfoIndex, CDCtx); + HTMLFile F = genInfoFile(InfoTitle, I->getRelativeFilePath(""), + MainContentNodes, InfoIndex, CDCtx); F.Render(OS); return llvm::Error::success(); @@ -902,7 +904,7 @@ J.attribute("USR", toHex(llvm::toStringRef(I.USR))); J.attribute("Name", I.Name); J.attribute("RefType", getRefType(I.RefType)); - J.attribute("Path", I.Path); + J.attribute("Path", I.getRelativeFilePath("")); J.attributeArray("Children", [&] { for (const Index &C : I.Children) IndexToJSON(C); diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp --- a/clang-tools-extra/clang-doc/MDGenerator.cpp +++ b/clang-tools-extra/clang-doc/MDGenerator.cpp @@ -50,10 +50,20 @@ OS << std::string(Num, '#') + " " + Text << "\n\n"; } -static void writeFileDefinition(const Location &L, raw_ostream &OS) { - OS << genItalic("Defined at line " + std::to_string(L.LineNumber) + " of " + - L.Filename) - << "\n\n"; +static void writeFileDefinition(const ClangDocContext &CDCtx, const Location &L, + raw_ostream &OS) { + + if (!CDCtx.RepositoryUrl) { + OS << "*Defined at " << L.Filename << "#" << std::to_string(L.LineNumber) + << "*"; + } else { + OS << "*Defined at [" << L.Filename << "#" << std::to_string(L.LineNumber) + << "](" << StringRef{CDCtx.RepositoryUrl.getValue()} + << llvm::sys::path::relative_path(L.Filename) << "#" + << std::to_string(L.LineNumber) << ")" + << "*"; + } + OS << "\n\n"; } static void writeDescription(const CommentInfo &I, raw_ostream &OS) { @@ -104,7 +114,16 @@ } } -static void genMarkdown(const EnumInfo &I, llvm::raw_ostream &OS) { +static void writeNameLink(const StringRef &CurrentPath, const Reference &R, + llvm::raw_ostream &OS) { + + llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath); + llvm::sys::path::append(Path, R.getFileBaseName() + ".md"); + OS << "[" << R.Name << "](" << Path << ")"; +} + +static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I, + llvm::raw_ostream &OS) { if (I.Scoped) writeLine("| enum class " + I.Name + " |", OS); else @@ -118,13 +137,14 @@ Members << "| " << N << " |\n"; writeLine(Members.str(), OS); if (I.DefLoc) - writeFileDefinition(I.DefLoc.getValue(), OS); + writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS); for (const auto &C : I.Description) writeDescription(C, OS); } -static void genMarkdown(const FunctionInfo &I, llvm::raw_ostream &OS) { +static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I, + llvm::raw_ostream &OS) { std::string Buffer; llvm::raw_string_ostream Stream(Buffer); bool First = true; @@ -145,13 +165,14 @@ Stream.str() + ")"), OS); if (I.DefLoc) - writeFileDefinition(I.DefLoc.getValue(), OS); + writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS); for (const auto &C : I.Description) writeDescription(C, OS); } -static void genMarkdown(const NamespaceInfo &I, llvm::raw_ostream &OS) { +static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I, + llvm::raw_ostream &OS) { if (I.Name == "") writeHeader("Global Namespace", 1, OS); else @@ -164,36 +185,47 @@ writeNewLine(OS); } + llvm::SmallString<64> BasePath = I.getRelativeFilePath(""); + if (!I.ChildNamespaces.empty()) { writeHeader("Namespaces", 2, OS); - for (const auto &R : I.ChildNamespaces) - writeLine(R.Name, OS); + for (const auto &R : I.ChildNamespaces) { + OS << "* "; + writeNameLink(BasePath, R, OS); + OS << "\n"; + } writeNewLine(OS); } + if (!I.ChildRecords.empty()) { writeHeader("Records", 2, OS); - for (const auto &R : I.ChildRecords) - writeLine(R.Name, OS); + for (const auto &R : I.ChildRecords) { + OS << "* "; + writeNameLink(BasePath, R, OS); + OS << "\n"; + } writeNewLine(OS); } + if (!I.ChildFunctions.empty()) { writeHeader("Functions", 2, OS); for (const auto &F : I.ChildFunctions) - genMarkdown(F, OS); + genMarkdown(CDCtx, F, OS); writeNewLine(OS); } if (!I.ChildEnums.empty()) { writeHeader("Enums", 2, OS); for (const auto &E : I.ChildEnums) - genMarkdown(E, OS); + genMarkdown(CDCtx, E, OS); writeNewLine(OS); } } -static void genMarkdown(const RecordInfo &I, llvm::raw_ostream &OS) { +static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I, + llvm::raw_ostream &OS) { writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS); if (I.DefLoc) - writeFileDefinition(I.DefLoc.getValue(), OS); + writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS); if (!I.Description.empty()) { for (const auto &C : I.Description) @@ -234,17 +266,86 @@ if (!I.ChildFunctions.empty()) { writeHeader("Functions", 2, OS); for (const auto &F : I.ChildFunctions) - genMarkdown(F, OS); + genMarkdown(CDCtx, F, OS); writeNewLine(OS); } if (!I.ChildEnums.empty()) { writeHeader("Enums", 2, OS); for (const auto &E : I.ChildEnums) - genMarkdown(E, OS); + genMarkdown(CDCtx, E, OS); writeNewLine(OS); } } +static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) { + // Write out the heading level starting at ## + OS << "##" << std::string(level, '#') << " "; + writeNameLink("", I, OS); + OS << "\n"; +} + +static llvm::Error serializeIndex(ClangDocContext &CDCtx) { + std::error_code FileErr; + llvm::SmallString<128> FilePath; + llvm::sys::path::native(CDCtx.OutDirectory, FilePath); + llvm::sys::path::append(FilePath, "all_files.md"); + llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None); + if (FileErr) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "error creating index file: " + + FileErr.message()); + + CDCtx.Idx.sort(); + OS << "# All Files"; + if (!CDCtx.ProjectName.empty()) + OS << " for " << CDCtx.ProjectName; + OS << "\n\n"; + + for (auto C : CDCtx.Idx.Children) + serializeReference(OS, C, 0); + + return llvm::Error::success(); +} + +static llvm::Error genIndex(ClangDocContext &CDCtx) { + std::error_code FileErr; + llvm::SmallString<128> FilePath; + llvm::sys::path::native(CDCtx.OutDirectory, FilePath); + llvm::sys::path::append(FilePath, "index.md"); + llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None); + if (FileErr) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "error creating index file: " + + FileErr.message()); + CDCtx.Idx.sort(); + OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n"; + for (auto C : CDCtx.Idx.Children) { + if (!C.Children.empty()) { + const char *Type; + switch (C.RefType) { + case InfoType::IT_namespace: + Type = "Namespace"; + break; + case InfoType::IT_record: + Type = "Type"; + break; + case InfoType::IT_enum: + Type = "Enum"; + break; + case InfoType::IT_function: + Type = "Function"; + break; + case InfoType::IT_default: + Type = "Other"; + } + OS << "* " << Type << ": [" << C.Name << "]("; + if (!C.Path.empty()) + OS << C.Path << "/"; + OS << C.Name << ")\n"; + } + } + return llvm::Error::success(); +} /// Generator for Markdown documentation. class MDGenerator : public Generator { public: @@ -252,6 +353,7 @@ llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, const ClangDocContext &CDCtx) override; + llvm::Error createResources(ClangDocContext &CDCtx) override; }; const char *MDGenerator::Format = "md"; @@ -260,16 +362,16 @@ const ClangDocContext &CDCtx) { switch (I->IT) { case InfoType::IT_namespace: - genMarkdown(*static_cast(I), OS); + genMarkdown(CDCtx, *static_cast(I), OS); break; case InfoType::IT_record: - genMarkdown(*static_cast(I), OS); + genMarkdown(CDCtx, *static_cast(I), OS); break; case InfoType::IT_enum: - genMarkdown(*static_cast(I), OS); + genMarkdown(CDCtx, *static_cast(I), OS); break; case InfoType::IT_function: - genMarkdown(*static_cast(I), OS); + genMarkdown(CDCtx, *static_cast(I), OS); break; case InfoType::IT_default: return createStringError(llvm::inconvertibleErrorCode(), @@ -278,11 +380,25 @@ return llvm::Error::success(); } +llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) { + // Write an all_files.md + auto Err = serializeIndex(CDCtx); + if (Err) + return Err; + + // Generate the index page. + Err = genIndex(CDCtx); + if (Err) + return Err; + + return llvm::Error::success(); +} + static GeneratorRegistry::Add MD(MDGenerator::Format, "Generator for MD output."); -// This anchor is used to force the linker to link in the generated object file -// and thus register the generator. +// This anchor is used to force the linker to link in the generated object +// file and thus register the generator. volatile int MDGeneratorAnchorSource = 0; } // namespace doc diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h --- a/clang-tools-extra/clang-doc/Representation.h +++ b/clang-tools-extra/clang-doc/Representation.h @@ -135,6 +135,12 @@ bool mergeable(const Reference &Other); void merge(Reference &&I); + /// Returns the path for this Reference relative to CurrentPath. + llvm::SmallString<64> getRelativeFilePath(const StringRef &CurrentPath) const; + + /// Returns the basename that should be used for this Reference. + llvm::SmallString<16> getFileBaseName() const; + SymbolID USR = SymbolID(); // Unique identifier for referenced decl SmallString<16> Name; // Name of type (possibly unresolved). InfoType RefType = InfoType::IT_default; // Indicates the type of this @@ -262,6 +268,12 @@ llvm::SmallString<16> extractName() const; + /// Returns the file path for this Info relative to CurrentPath. + llvm::SmallString<64> getRelativeFilePath(const StringRef &CurrentPath) const; + + /// Returns the basename that should be used for this Info. + llvm::SmallString<16> getFileBaseName() const; + // Returns a reference to the parent scope (that is, the immediate parent // namespace or class in which this decl resides). llvm::Expected getEnclosingScope(); diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp --- a/clang-tools-extra/clang-doc/Representation.cpp +++ b/clang-tools-extra/clang-doc/Representation.cpp @@ -114,6 +114,52 @@ } } +static llvm::SmallString<64> +calculateRelativeFilePath(const InfoType &Type, const StringRef &Path, + const StringRef &Name, const StringRef &CurrentPath) { + llvm::SmallString<64> FilePath; + + if (CurrentPath != Path) { + // iterate back to the top + for (llvm::sys::path::const_iterator I = + llvm::sys::path::begin(CurrentPath); + I != llvm::sys::path::end(CurrentPath); ++I) + llvm::sys::path::append(FilePath, ".."); + llvm::sys::path::append(FilePath, Path); + } + + // Namespace references have a Path to the parent namespace, but + // the file is actually in the subdirectory for the namespace. + if (Type == doc::InfoType::IT_namespace) + llvm::sys::path::append(FilePath, Name); + + return llvm::sys::path::relative_path(FilePath); +} + +llvm::SmallString<64> +Reference::getRelativeFilePath(const StringRef &CurrentPath) const { + return calculateRelativeFilePath(RefType, Path, Name, CurrentPath); +} + +llvm::SmallString<16> Reference::getFileBaseName() const { + if (RefType == InfoType::IT_namespace) + return llvm::SmallString<16>("index"); + + return Name; +} + +llvm::SmallString<64> +Info::getRelativeFilePath(const StringRef &CurrentPath) const { + return calculateRelativeFilePath(IT, Path, extractName(), CurrentPath); +} + +llvm::SmallString<16> Info::getFileBaseName() const { + if (IT == InfoType::IT_namespace) + return llvm::SmallString<16>("index"); + + return extractName(); +} + bool Reference::mergeable(const Reference &Other) { return RefType == Other.RefType && USR == Other.USR; } diff --git a/clang-tools-extra/clang-doc/assets/index.js b/clang-tools-extra/clang-doc/assets/index.js --- a/clang-tools-extra/clang-doc/assets/index.js +++ b/clang-tools-extra/clang-doc/assets/index.js @@ -31,7 +31,11 @@ function genLink(Ref, CurrentDirectory) { var Path = computeRelativePath(Ref.Path, CurrentDirectory); - Path = append(Path, Ref.Name + ".html") + if (Ref.RefType == "namespace") + Path = append(Path, "index.html"); + else + Path = append(Path, Ref.Name + ".html") + ANode = document.createElement("a"); ANode.setAttribute("href", Path); var TextNode = document.createTextNode(Ref.Name); diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -294,8 +294,9 @@ } doc::Info *I = Reduced.get().get(); - auto InfoPath = getInfoOutputFile(OutDirectory, I->Path, I->extractName(), - "." + Format); + auto InfoPath = + getInfoOutputFile(OutDirectory, I->getRelativeFilePath(""), + I->getFileBaseName(), "." + Format); if (!InfoPath) { llvm::errs() << toString(InfoPath.takeError()) << "\n"; Error = true; @@ -304,9 +305,9 @@ std::error_code FileErr; llvm::raw_fd_ostream InfoOS(InfoPath.get(), FileErr, llvm::sys::fs::OF_None); - if (FileErr != OK) { - llvm::errs() << "Error opening info file: " << FileErr.message() - << "\n"; + if (FileErr) { + llvm::errs() << "Error opening info file " << InfoPath.get() << ": " + << FileErr.message() << "\n"; return; }