Skip to content

Commit 6e1f4c4

Browse files
committed
[clang-doc] lift Mustache template generation from HTML
To prepare for more backends to use Mustache templates, this patch lifts the Mustache functionality from HTMLMustacheGenerator.cpp to Generators.h. A MustacheGenerator interface is created to share code for template creation.
1 parent 73e70e0 commit 6e1f4c4

File tree

3 files changed

+243
-170
lines changed

3 files changed

+243
-170
lines changed

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

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "Generators.h"
10+
#include "support/File.h"
11+
#include "llvm/Support/TimeProfiler.h"
1012

1113
LLVM_INSTANTIATE_REGISTRY(clang::doc::GeneratorRegistry)
1214

15+
using namespace llvm;
16+
using namespace llvm::json;
17+
using namespace llvm::mustache;
18+
1319
namespace clang {
1420
namespace doc {
1521

@@ -42,6 +48,136 @@ std::string getTagType(TagTypeKind AS) {
4248
llvm_unreachable("Unknown TagTypeKind");
4349
}
4450

51+
Error createFileOpenError(StringRef FileName, std::error_code EC) {
52+
return createFileError("cannot open file " + FileName, EC);
53+
}
54+
55+
Error MustacheGenerator::setupTemplate(
56+
std::unique_ptr<MustacheTemplateFile> &Template, StringRef TemplatePath,
57+
std::vector<std::pair<StringRef, StringRef>> Partials) {
58+
auto T = MustacheTemplateFile::createMustacheFile(TemplatePath);
59+
if (Error Err = T.takeError())
60+
return Err;
61+
Template = std::move(T.get());
62+
for (const auto &[Name, FileName] : Partials)
63+
if (auto Err = Template->registerPartialFile(Name, FileName))
64+
return Err;
65+
return Error::success();
66+
}
67+
68+
Error MustacheGenerator::generateDocumentation(
69+
StringRef RootDir, StringMap<std::unique_ptr<doc::Info>> Infos,
70+
const clang::doc::ClangDocContext &CDCtx, std::string DirName) {
71+
{
72+
llvm::TimeTraceScope TS("Setup Templates");
73+
if (auto Err = setupTemplateFiles(CDCtx))
74+
return Err;
75+
}
76+
77+
{
78+
llvm::TimeTraceScope TS("Generate JSON for Mustache");
79+
if (auto JSONGenerator = findGeneratorByName("json")) {
80+
if (Error Err = JSONGenerator.get()->generateDocs(
81+
RootDir, std::move(Infos), CDCtx))
82+
return Err;
83+
} else
84+
return JSONGenerator.takeError();
85+
}
86+
87+
SmallString<128> JSONPath;
88+
sys::path::native(RootDir.str() + "/json", JSONPath);
89+
90+
{
91+
llvm::TimeTraceScope TS("Iterate JSON files");
92+
std::error_code EC;
93+
sys::fs::recursive_directory_iterator JSONIter(JSONPath, EC);
94+
std::vector<json::Value> JSONFiles;
95+
JSONFiles.reserve(Infos.size());
96+
if (EC)
97+
return createStringError("Failed to create directory iterator.");
98+
99+
SmallString<128> DocsDirPath(RootDir.str() + '/' + DirName);
100+
sys::path::native(DocsDirPath);
101+
if (auto EC = sys::fs::create_directories(DocsDirPath))
102+
return createFileError(DocsDirPath, EC);
103+
while (JSONIter != sys::fs::recursive_directory_iterator()) {
104+
// create the same directory structure in the docs format dir
105+
if (JSONIter->type() == sys::fs::file_type::directory_file) {
106+
SmallString<128> DocsClonedPath(JSONIter->path());
107+
sys::path::replace_path_prefix(DocsClonedPath, JSONPath, DocsDirPath);
108+
if (auto EC = sys::fs::create_directories(DocsClonedPath)) {
109+
return createFileError(DocsClonedPath, EC);
110+
}
111+
}
112+
113+
if (EC)
114+
return createFileError("Failed to iterate: " + JSONIter->path(), EC);
115+
116+
auto Path = StringRef(JSONIter->path());
117+
if (!Path.ends_with(".json")) {
118+
JSONIter.increment(EC);
119+
continue;
120+
}
121+
122+
auto File = MemoryBuffer::getFile(Path);
123+
if (EC = File.getError(); EC) {
124+
// TODO: Buffer errors to report later, look into using Clang
125+
// diagnostics.
126+
llvm::errs() << "Failed to open file: " << Path << " " << EC.message()
127+
<< '\n';
128+
}
129+
130+
auto Parsed = json::parse((*File)->getBuffer());
131+
if (!Parsed)
132+
return Parsed.takeError();
133+
auto ValidJSON = Parsed.get();
134+
135+
std::error_code FileErr;
136+
SmallString<128> DocsFilePath(JSONIter->path());
137+
sys::path::replace_path_prefix(DocsFilePath, JSONPath, DocsDirPath);
138+
sys::path::replace_extension(DocsFilePath, DirName);
139+
raw_fd_ostream InfoOS(DocsFilePath, FileErr, sys::fs::OF_None);
140+
if (FileErr)
141+
return createFileOpenError(Path, FileErr);
142+
143+
auto RelativeRootPath = getRelativePathToRoot(DocsFilePath, DocsDirPath);
144+
auto InfoTypeStr =
145+
getInfoTypeStr(Parsed->getAsObject(), sys::path::stem(DocsFilePath));
146+
if (!InfoTypeStr)
147+
return InfoTypeStr.takeError();
148+
if (Error Err = generateDocForJSON(*Parsed, InfoOS, CDCtx,
149+
InfoTypeStr.get(), RelativeRootPath))
150+
return Err;
151+
JSONIter.increment(EC);
152+
}
153+
}
154+
155+
return Error::success();
156+
}
157+
158+
Expected<std::string> MustacheGenerator::getInfoTypeStr(Object *Info,
159+
StringRef Filename) {
160+
auto StrValue = (*Info)["InfoType"];
161+
if (StrValue.kind() != json::Value::Kind::String)
162+
return createStringError("JSON file '%s' does not contain key: 'InfoType'.",
163+
Filename.str().c_str());
164+
auto ObjTypeStr = StrValue.getAsString();
165+
if (!ObjTypeStr.has_value())
166+
return createStringError(
167+
"JSON file '%s' does not contain 'InfoType' field as a string.",
168+
Filename.str().c_str());
169+
return ObjTypeStr.value().str();
170+
}
171+
172+
SmallString<128>
173+
MustacheGenerator::getRelativePathToRoot(StringRef PathToFile,
174+
StringRef DocsRootPath) {
175+
SmallString<128> PathVec(PathToFile);
176+
// Remove filename, or else the relative path will have an extra "../"
177+
sys::path::remove_filename(PathVec);
178+
return computeRelativePath(DocsRootPath, PathVec);
179+
}
180+
45181
llvm::Error Generator::createResources(ClangDocContext &CDCtx) {
46182
return llvm::Error::success();
47183
}

clang-tools-extra/clang-doc/Generators.h

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
#include "Representation.h"
1616
#include "llvm/Support/Error.h"
17+
#include "llvm/Support/JSON.h"
18+
#include "llvm/Support/Mustache.h"
1719
#include "llvm/Support/Registry.h"
1820

1921
namespace clang {
@@ -52,6 +54,83 @@ findGeneratorByName(llvm::StringRef Format);
5254

5355
std::string getTagType(TagTypeKind AS);
5456

57+
llvm::Error createFileOpenError(StringRef FileName, std::error_code EC);
58+
59+
class MustacheTemplateFile {
60+
llvm::BumpPtrAllocator Allocator;
61+
llvm::StringSaver Saver;
62+
llvm::mustache::MustacheContext Ctx;
63+
llvm::mustache::Template T;
64+
std::unique_ptr<llvm::MemoryBuffer> Buffer;
65+
66+
public:
67+
static Expected<std::unique_ptr<MustacheTemplateFile>>
68+
createMustacheFile(StringRef FileName) {
69+
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferOrError =
70+
llvm::MemoryBuffer::getFile(FileName);
71+
if (auto EC = BufferOrError.getError())
72+
return createFileOpenError(FileName, EC);
73+
return std::make_unique<MustacheTemplateFile>(
74+
std::move(BufferOrError.get()));
75+
}
76+
77+
llvm::Error registerPartialFile(StringRef Name, StringRef FileName) {
78+
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferOrError =
79+
llvm::MemoryBuffer::getFile(FileName);
80+
if (auto EC = BufferOrError.getError())
81+
return createFileOpenError(FileName, EC);
82+
83+
std::unique_ptr<llvm::MemoryBuffer> Buffer = std::move(BufferOrError.get());
84+
StringRef FileContent = Buffer->getBuffer();
85+
T.registerPartial(Name.str(), FileContent.str());
86+
return llvm::Error::success();
87+
}
88+
89+
void render(llvm::json::Value &V, raw_ostream &OS) { T.render(V, OS); }
90+
91+
MustacheTemplateFile(std::unique_ptr<llvm::MemoryBuffer> &&B)
92+
: Saver(Allocator), Ctx(Allocator, Saver), T(B->getBuffer(), Ctx),
93+
Buffer(std::move(B)) {}
94+
};
95+
96+
struct MustacheGenerator {
97+
Expected<std::string> getInfoTypeStr(llvm::json::Object *Info, StringRef Filename);
98+
99+
/// Used to find the relative path from the file to the format's docs root.
100+
/// Mainly used for the HTML resource paths.
101+
SmallString<128> getRelativePathToRoot(StringRef PathToFile,
102+
StringRef DocsRootPath);
103+
virtual ~MustacheGenerator() = default;
104+
105+
/// Initializes the template files from disk and calls setupTemplate to
106+
/// register partials
107+
virtual llvm::Error setupTemplateFiles(const ClangDocContext &CDCtx) = 0;
108+
109+
/// Populates templates with data from JSON and calls any specifics for the
110+
/// format. For example, for HTML it will render the paths for CSS and JS.
111+
virtual llvm::Error generateDocForJSON(llvm::json::Value &JSON, llvm::raw_fd_ostream &OS,
112+
const ClangDocContext &CDCtx,
113+
StringRef ObjectTypeStr,
114+
StringRef RelativeRootPath) = 0;
115+
116+
/// Registers partials to templates.
117+
llvm::Error setupTemplate(std::unique_ptr<MustacheTemplateFile> &Template,
118+
StringRef TemplatePath,
119+
std::vector<std::pair<StringRef, StringRef>> Partials);
120+
121+
/// \brief The main orchestrator for Mustache-based documentation.
122+
///
123+
/// 1. Initializes templates files from disk by calling setupTemplateFiles.
124+
/// 2. Calls the JSON generator to write JSON to disk.
125+
/// 3. Iterates over the JSON files, recreates the directory structure from
126+
/// JSON, and calls generateDocForJSON for each file.
127+
/// 4. A file of the desired format is created.
128+
llvm::Error generateDocumentation(StringRef RootDir,
129+
llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
130+
const clang::doc::ClangDocContext &CDCtx,
131+
std::string DirName);
132+
};
133+
55134
// This anchor is used to force the linker to link in the generated object file
56135
// and thus register the generators.
57136
extern volatile int YAMLGeneratorAnchorSource;

0 commit comments

Comments
 (0)