Skip to content

Commit 1086fe7

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 bc27843 commit 1086fe7

File tree

3 files changed

+242
-175
lines changed

3 files changed

+242
-175
lines changed

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

Lines changed: 131 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,135 @@ 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+
StringMap<json::Value> JSONFileMap;
87+
{
88+
llvm::TimeTraceScope TS("Iterate JSON files");
89+
std::error_code EC;
90+
sys::fs::recursive_directory_iterator JSONIter(JSONPath, EC);
91+
std::vector<json::Value> JSONFiles;
92+
JSONFiles.reserve(Infos.size());
93+
if (EC)
94+
return createStringError("Failed to create directory iterator.");
95+
96+
SmallString<128> DocsDirPath(RootDir.str() + '/' + DirName);
97+
sys::path::native(DocsDirPath);
98+
if (auto EC = sys::fs::create_directories(DocsDirPath))
99+
return createFileError(DocsDirPath, EC);
100+
while (JSONIter != sys::fs::recursive_directory_iterator()) {
101+
// create the same directory structure in the docs format dir
102+
if (JSONIter->type() == sys::fs::file_type::directory_file) {
103+
SmallString<128> DocsClonedPath(JSONIter->path());
104+
sys::path::replace_path_prefix(DocsClonedPath, JSONPath, DocsDirPath);
105+
if (auto EC = sys::fs::create_directories(DocsClonedPath))
106+
return createFileError(DocsClonedPath, EC);
107+
}
108+
109+
if (EC)
110+
return createFileError("Failed to iterate: " + JSONIter->path(), EC);
111+
112+
auto Path = StringRef(JSONIter->path());
113+
if (!Path.ends_with(".json")) {
114+
JSONIter.increment(EC);
115+
continue;
116+
}
117+
118+
auto File = MemoryBuffer::getFile(Path);
119+
if (EC = File.getError(); EC)
120+
// TODO: Buffer errors to report later, look into using Clang
121+
// diagnostics.
122+
llvm::errs() << "Failed to open file: " << Path << " " << EC.message()
123+
<< '\n';
124+
125+
auto Parsed = json::parse((*File)->getBuffer());
126+
if (!Parsed)
127+
return Parsed.takeError();
128+
auto ValidJSON = Parsed.get();
129+
130+
std::error_code FileErr;
131+
SmallString<128> DocsFilePath(JSONIter->path());
132+
sys::path::replace_path_prefix(DocsFilePath, JSONPath, DocsDirPath);
133+
sys::path::replace_extension(DocsFilePath, DirName);
134+
raw_fd_ostream InfoOS(DocsFilePath, FileErr, sys::fs::OF_None);
135+
if (FileErr)
136+
return createFileOpenError(Path, FileErr);
137+
138+
auto RelativeRootPath = getRelativePathToRoot(DocsFilePath, DocsDirPath);
139+
auto InfoTypeStr =
140+
getInfoTypeStr(Parsed->getAsObject(), sys::path::stem(DocsFilePath));
141+
if (!InfoTypeStr)
142+
return InfoTypeStr.takeError();
143+
if (Error Err = generateDocForJSON(*Parsed, InfoOS, CDCtx,
144+
InfoTypeStr.get(), RelativeRootPath))
145+
return Err;
146+
JSONIter.increment(EC);
147+
}
148+
}
149+
150+
return Error::success();
151+
}
152+
153+
Expected<std::string> MustacheGenerator::getInfoTypeStr(Object *Info,
154+
StringRef Filename) {
155+
auto StrValue = (*Info)["InfoType"];
156+
if (StrValue.kind() != json::Value::Kind::String)
157+
return createStringError("JSON file '%s' does not contain key: 'InfoType'.",
158+
Filename.str().c_str());
159+
auto ObjTypeStr = StrValue.getAsString();
160+
if (!ObjTypeStr.has_value())
161+
return createStringError(
162+
"JSON file '%s' does not contain 'InfoType' field as a string.",
163+
Filename.str().c_str());
164+
return ObjTypeStr.value().str();
165+
}
166+
167+
SmallString<128>
168+
MustacheGenerator::getRelativePathToRoot(StringRef PathToFile,
169+
StringRef DocsRootPath) {
170+
SmallString<128> PathVec(PathToFile);
171+
// Remove filename, or else the relative path will have an extra "../"
172+
sys::path::remove_filename(PathVec);
173+
return computeRelativePath(DocsRootPath, PathVec);
174+
}
175+
45176
llvm::Error Generator::createResources(ClangDocContext &CDCtx) {
46177
return llvm::Error::success();
47178
}

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)