Skip to content

Commit 7aaa6b7

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 7aaa6b7

File tree

3 files changed

+241
-174
lines changed

3 files changed

+241
-174
lines changed

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

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
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

@@ -42,6 +44,134 @@ std::string getTagType(TagTypeKind AS) {
4244
llvm_unreachable("Unknown TagTypeKind");
4345
}
4446

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

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

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@
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

21+
using namespace llvm;
22+
using namespace llvm::json;
23+
using namespace llvm::mustache;
24+
1925
namespace clang {
2026
namespace doc {
2127

@@ -52,6 +58,83 @@ findGeneratorByName(llvm::StringRef Format);
5258

5359
std::string getTagType(TagTypeKind AS);
5460

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

0 commit comments

Comments
 (0)