Skip to content

Commit ad6d576

Browse files
committed
feat: tagfiles generation for cross-referencing in doxygen format
1 parent 8693504 commit ad6d576

File tree

10 files changed

+731
-17
lines changed

10 files changed

+731
-17
lines changed

docs/mrdocs.schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,12 @@
244244
"title": "System include paths",
245245
"type": "array"
246246
},
247+
"tagfile-path": {
248+
"default": "<output>/reference.tag.xml",
249+
"description": "Specifies the full path (filename) where the generated tagfile should be saved. If left empty, no tagfile will be generated.",
250+
"title": "Path for the tagfile",
251+
"type": "string"
252+
},
247253
"use-system-stdlib": {
248254
"default": false,
249255
"description": "True if the compiler has to use just the system standard library. When set to true, the compiler uses the system standard library instead of the standard library provided by the compiler.",

include/mrdocs/Corpus.hpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,39 @@ class MRDOCS_VISIBLE
159159
}
160160
}
161161

162+
/** Visit the members of specified Info.
163+
164+
This function iterates the members of the specified
165+
Info `I`. For each member associated with a
166+
function with the same name as the member, the
167+
function object `f` is invoked with the member
168+
as the first argument, followed by `args...`.
169+
170+
When there are more than one member function
171+
with the same name, the function object `f` is
172+
invoked with an @ref OverloadSet as the first
173+
argument, followed by `args...`.
174+
175+
@param I The Info to traverse.
176+
@param pred The predicate to use to determine if the member should be visited.
177+
@param f The function to invoke with the member as the first argument, followed by `args...`.
178+
@param args The arguments to pass to the function.
179+
*/
180+
template <InfoParent T, class Pred, class F, class... Args>
181+
void
182+
traverseIf(
183+
T const& I, Pred&& pred, F&& f, Args&&... args) const
184+
{
185+
for (auto const& id : I.Members)
186+
{
187+
if (std::forward<Pred>(pred)(get(id)))
188+
{
189+
visit(get(id), std::forward<F>(f),
190+
std::forward<Args>(args)...);
191+
}
192+
}
193+
}
194+
162195
/** Visit the member overloads of specified ScopeInfo.
163196
164197
This function iterates the members of the

include/mrdocs/Generator.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,29 @@ class MRDOCS_VISIBLE
182182
Corpus const& corpus) const;
183183
};
184184

185+
/** Return the full path for single page output.
186+
187+
This function determines the full path for a single-page output file
188+
based on the provided `outputPath` and file `extension`.
189+
190+
If the `outputPath` already exists:
191+
- If it is a directory, appends the default file name with the provided extension.
192+
- If it is a file, uses the provided `outputPath` directly.
193+
194+
If the `outputPath` does not exist:
195+
- If it ends with a '/', assumes it is a directory and appends the default file name.
196+
- Otherwise, it returns an error because the path is ambiguous.
197+
198+
@return The full path or an error if the `outputPath` is ambiguous.
199+
200+
@param outputPath The specified output path, which can be a directory or file.
201+
@param extension The file extension to use for single-page output.
202+
*/
203+
Expected<std::string>
204+
getSinglePageFullPath(
205+
std::string_view outputPath,
206+
std::string_view extension);
207+
185208
} // mrdocs
186209
} // clang
187210

src/lib/Gen/hbs/HandlebarsGenerator.cpp

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,17 @@
1515
#include "Builder.hpp"
1616
#include "MultiPageVisitor.hpp"
1717
#include "SinglePageVisitor.hpp"
18+
19+
#include "lib/Lib/TagfileWriter.hpp"
20+
#include "lib/Support/RawOstream.hpp"
21+
22+
#include <llvm/ADT/SmallString.h>
23+
#include <llvm/Support/FileSystem.h>
24+
#include <llvm/Support/Path.h>
25+
1826
#include <mrdocs/Support/Path.hpp>
27+
28+
#include <fstream>
1929
#include <sstream>
2030

2131
namespace clang {
@@ -30,6 +40,30 @@ createEscapeFn(HandlebarsGenerator const& gen)
3040
};
3141
}
3242

43+
namespace {
44+
45+
/** Generate a Tagfile to enable cross-referencing between documentation symbols. */
46+
Expected<void>
47+
generateTagfile(
48+
HandlebarsCorpus const& hbsCorpus,
49+
std::string_view tagFilePath,
50+
std::string_view outputFilesBasePath
51+
)
52+
{
53+
MRDOCS_ASSERT(!tagFilePath.empty());
54+
55+
auto tagFileWriterEx = TagfileWriter::create(hbsCorpus, tagFilePath, outputFilesBasePath);
56+
MRDOCS_CHECK_OR(tagFileWriterEx, Unexpected(tagFileWriterEx.error()));
57+
58+
auto tagfileWriter = std::move(*tagFileWriterEx);
59+
tagfileWriter.initialize();
60+
visit(hbsCorpus->globalNamespace(), tagfileWriter);
61+
tagfileWriter.finalize();
62+
return {};
63+
}
64+
65+
} // anonymous namespace
66+
3367
Expected<ExecutorGroup<Builder>>
3468
createExecutors(
3569
HandlebarsGenerator const& gen,
@@ -79,7 +113,17 @@ build(
79113
{
80114
if (!corpus.config->multipage)
81115
{
82-
return Generator::build(outputPath, corpus);
116+
auto e = Generator::build(outputPath, corpus);
117+
if (e.has_value() && !corpus.config->tagfilePath.empty())
118+
{
119+
// Generate tagfile if specified
120+
auto const singlePageFileName = getSinglePageFullPath(outputPath, fileExtension());
121+
MRDOCS_CHECK_OR(singlePageFileName, Unexpected(singlePageFileName.error()));
122+
HandlebarsCorpus hbsCorpus = createDomCorpus(*this, corpus);
123+
return generateTagfile(hbsCorpus, corpus.config->tagfilePath, *singlePageFileName);
124+
}
125+
126+
return e;
83127
}
84128

85129
// Create corpus and executors
@@ -93,6 +137,12 @@ build(
93137
// Wait for all executors to finish and check errors
94138
auto errors = ex.wait();
95139
MRDOCS_CHECK_OR(errors.empty(), Unexpected(errors));
140+
141+
if (! corpus.config->tagfilePath.empty())
142+
{
143+
return generateTagfile(domCorpus, corpus.config->tagfilePath, outputPath);
144+
}
145+
96146
return {};
97147
}
98148

src/lib/Gen/xml/XMLTags.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ void
193193
XMLTags::
194194
nest(int levels)
195195
{
196+
if (!nesting_)
197+
return;
198+
196199
if(levels > 0)
197200
{
198201
indent_.append(levels * 2, ' ');

src/lib/Gen/xml/XMLTags.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ class XMLTags
186186
public:
187187
std::string indent_;
188188
llvm::raw_ostream& os_;
189+
bool nesting_ = true;
189190

190191
explicit
191192
XMLTags(
@@ -201,6 +202,7 @@ class XMLTags
201202
void write(dom::String const&,
202203
llvm::StringRef value = {}, Attributes = {});
203204
void close(dom::String const&);
205+
void nesting(bool enable) noexcept { nesting_ = enable; }
204206

205207
void nest(int levels);
206208
};

src/lib/Lib/ConfigOptions.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,15 @@
148148
"default": "<mrdocs-root>/share/mrdocs/addons",
149149
"relativeto": "<config-dir>"
150150
},
151+
{
152+
"name": "tagfile-path",
153+
"brief": "Path for the tagfile",
154+
"details": "Specifies the full path (filename) where the generated tagfile should be saved. If left empty, no tagfile will be generated.",
155+
"type": "file-path",
156+
"default": "<output>/reference.tag.xml",
157+
"relativeto": "<output>",
158+
"must-exist": false
159+
},
151160
{
152161
"name": "legible-names",
153162
"brief": "Use legible names",

0 commit comments

Comments
 (0)