Doxygen XLinks
by
V: 2511R0
Website: doxygen
Loading...
Searching...
No Matches
doxyfile.cpp
1//==================================================================================================
2// This implementation-file is part of DoxygenXLinks - A doxygen post-processor that allows to
3// define smarter <b>Doxygen</b>-links.
4//
5// \emoji :copyright: 2025-2026 A-Worx GmbH, Germany.
6// Published under \ref mainpage_license "Boost Software License".
7//==================================================================================================
8#include "doxyfile.hpp"
9#include "dxl.hpp"
10#include "ALib.ALox.H"
13#include "ALib.App.H"
14#include <fstream>
15
16
17using namespace alib;
18using namespace std;
19
20namespace dxl {
21
22void DoxygenINIFile::Load(const system::Path& filePath) {
23 Lox_SetDomain("DXL/DOXYFILE", Scope::Filename )
24 Lox_Info("Reading doxygen ini file: ", filePath )
25
26 std::ifstream ifstream;
27 errno= 0;
28 ifstream.open( filePath.Terminate() );
29 if ( !ifstream.is_open() || errno ) {
31 app.cErr->Add(app.cli.ExitCodeDecls.Find(ExitCodes::CantOpenDoxyfile).Mapped()->FormatString(),
32 filePath);
33 app.machine.SetExitCode(ExitCodes::CantOpenDoxyfile);
34 throw Exception( ALIB_CALLER_NULLED, app::App::Exceptions::ControlledEarlyExit );
35 }
36
37 // get the allocator just from any of our containers
38 MonoAllocator& ma= InputPaths.GetAllocator();
39
40 // reader
41 IStreamReader reader;
42 reader.SetStream( &ifstream );
44
45 // loop over all lines of the HTML-file
46 // We read logical lines: handle trailing '\\' for continuations and strip comments '#'
48 system::Path tmpOutputDirectory; // needed to resolve HTML_OUTPUT
49 system::Path tmpHtmlOutput;
50 system::Path tmpTagFilePath; // will be added to the list of tagfiles at the end
51 // when the output path is known
52 bool tmpCreateSubDirs= false; // needed to resolve CREATE_SUBDIRS_LEVEL
53
54 auto finalizeLogicalLine = [&](Substring logical) {
55 // remove in-line comments starting with '#' (when not inside quotes)
56 bool inQuotes = false;
57 int cutPos = -1;
58 for (int i = 0; i < logical.Length(); ++i) {
59 nchar c = logical.CharAt(i);
60 if (c == '"') inQuotes = !inQuotes;
61 if (c == '#' && !inQuotes) { cutPos = i; break; }
62 }
63 if (cutPos >= 0)
64 logical = logical.Substring(0, cutPos);
65 logical.Trim();
66 if (logical.IsEmpty())
67 return;
68
69 // Expect KEY = VALUE
70 integer eqPos = logical.IndexOf('=');
71 if (eqPos < 0) return; // ignore malformed lines
72
73 Substring key = logical.Substring(0, eqPos); key.Trim();
74 Substring val = logical.Substring(eqPos + 1); val.Trim();
75
76 auto stripQuotes = [](Substring& s){
77 s.Trim();
78 if (s.Length() >= 2 && s.CharAtStart() == '"' && s.CharAtEnd() == '"') {
79 s.ConsumeChar('"');
80 // remove last quote by creating a shorter substring
81 s = s.Substring(0, s.Length()-1);
82 }
83 };
84
85 auto addListTokensStr = [&](Substring src,
86 ListMA<String>& listOut) {
87 while (src.IsNotNull()) {
88 src.TrimStart();
89 if (src.IsEmpty()) break;
90 if (src.ConsumeChar('"')) {
91 integer end = src.IndexOf('"');
92 Substring tok;
93 if (end >= 0) { tok = src.Substring(0, end); src.ConsumeChars(end); src.ConsumeChar('"'); }
94 else { tok = src; src.ConsumeChars(src.Length()); }
95 tok.Trim();
96 if (tok.IsNotEmpty()) listOut.emplace_back(String(ma,tok));
97 } else {
98 Substring tok = src.ConsumeToken(' ');
99 tok.Trim();
100 if (tok.IsNotEmpty()) listOut.emplace_back(String(ma,tok));
101 }
102 }
103 };
104
105 auto addListTokensPath = [&](Substring src,
107 while (src.IsNotNull()) {
108 src.TrimStart();
109 if (src.IsEmpty()) break;
110 if (src.CharAtStart() == '"') {
111 src.ConsumeChar('"');
112 integer end = src.IndexOf('"');
113 Substring tok;
114 if (end >= 0) { tok = src.Substring(0, end); src.ConsumeChars(end); src.ConsumeChar('"'); }
115 else { tok = src; src.ConsumeChars(src.Length()); }
116 tok.Trim();
117 if (tok.IsNotEmpty()) { system::PathString p; p= tok; listOut.emplace_back(String(ma,p)); }
118 } else {
119 Substring tok = src.ConsumeToken(' ');
120 tok.Trim();
121 if (tok.IsNotEmpty()) { system::PathString p; p= tok; listOut.emplace_back(String(ma,p)); }
122 }
123 }
124 };
125
126 // Dispatch on key
127 if (key.Equals("OUTPUT_DIRECTORY")) {
128 stripQuotes(val);
129 tmpOutputDirectory = val;
130 }
131 else if (key.Equals("HTML_OUTPUT")) {
132 stripQuotes(val);
133 // Temporarily store raw value in HtmlOutput; resolve after loop using outputDirectory
134 tmpHtmlOutput = val;
135 }
136 else if (key.Equals("HTML_FILE_EXTENSION")) { stripQuotes(val); HtmlFileExtension = String(ma,val); }
137 else if (key.Equals("CREATE_SUBDIRS")) { tmpCreateSubDirs = val.Equals<CHK,lang::Case::Ignore>("YES"); }
138 else if (key.Equals("CREATE_SUBDIRS_LEVEL")){ val.ConsumeDec(HtmlSubfolderDepth); }
139 else if (key.Equals("INPUT")) { addListTokensPath(val, InputPaths); }
140 else if (key.Equals("RECURSIVE")) { RecursiveInputScan= val.Equals<CHK,lang::Case::Ignore>("YES"); }
141 else if (key.Equals("EXCLUDE")) { addListTokensPath(val, ExcludePaths); }
142 else if (key.Equals("EXCLUDE_SYMLINKS")) { ExcludeSymlinks = val.Equals<CHK,lang::Case::Ignore>("YES"); }
143 else if (key.Equals("EXCLUDE_PATTERNS")) { addListTokensStr (val, ExcludePatterns); }
144 else if (key.Equals("FILE_PATTERNS")) {
145 String512 buf;
146 while ( val.IsNotEmpty() ) {
147 val.TrimStart("* \t\r");
148 buf << val.ConsumeToken(' ') << ' ';
149 }
150 FilePatterns= String(ma, buf);
151 }
152 else if (key.Equals("TAGFILES")) {
153 // tokens of form file or file=loc (file/location may be quoted)
154 Substring rest(val);
155 while (rest.IsNotNull()) {
156 rest.TrimStart();
157 if (rest.IsEmpty()) break;
158
159 Substring token;
160 if (rest.CharAtStart() == '"') {
161 rest.ConsumeChar('"');
162 integer end = rest.IndexOf('"');
163 if (end >= 0) {
164 token = rest.Substring(0, end);
165 rest.ConsumeChars(end);
166 rest.ConsumeChar('"');
167 } else {
168 token = rest; rest.ConsumeChars(rest.Length());
169 }
170 } else {
171 token = rest.ConsumeToken();
172 }
173 token.Trim();
174 if (token.IsEmpty()) continue;
175
176 // split at '=' if present
177 integer eq = token.IndexOf('=');
178 TagFileInfo tfInfo;
179 if (eq >= 0) {
180 Substring f = token.Substring(0, eq); f.Trim();
181 Substring l = token.Substring(eq+1); l.Trim();
182 tfInfo.TagFilePath = String(ma,f);
183 tfInfo.BaseURL = String(ma,l);
184 } else {
185 tfInfo.TagFilePath = String(ma,token);
186 }
187 TagFiles.emplace_back(tfInfo);
188 }
189 }
190 else if (key.Equals("GENERATE_TAGFILE")) {
191 stripQuotes(val);
192 if (val.IsNotEmpty())
193 tmpTagFilePath= val;
194 }
195 };
196
197 while( !reader.IsEOF() ) {
198 reader.Read(lineBuf);
199
200 Substring line(lineBuf);
201 // append to accumulator
202 acc._(line);
203
204 // check for line continuation: trailing '\\' after trimming right spaces
205 Substring accView(acc);
206 accView.TrimEnd();
207 bool cont = accView.IsNotEmpty() && accView.CharAtEnd() == '\\';
208 if (cont) {
209 // remove trailing backslash and continue accumulating
210 acc.SetLength(acc.Length()-1);
211 acc._(' ');
212 continue;
213 }
214
215 // process logical line in acc
216 Substring logical(acc);
217 finalizeLogicalLine(logical);
218 acc.Reset();
219 }
220
221 if (tmpTagFilePath.IsEmpty()) {
222 app::Get<DXLApp>().cErr->Add(app::Get<DXLApp>().cli.ExitCodeDecls.Find(ExitCodes::NoTagfileGeneratedByDoxyfile).Mapped()->FormatString(),
223 filePath);
225 throw Exception( ALIB_CALLER_NULLED, app::App::Exceptions::ControlledEarlyExit );
226 }
227
228 // Resolve the HTML output directory relative to OUTPUT_DIRECTORY if not absolute
229 if (tmpHtmlOutput.IsAbsolute())
230 HTMLFilePath= String(ma,tmpHtmlOutput);
231 else {
232 tmpOutputDirectory << system::DIRECTORY_SEPARATOR << tmpHtmlOutput;
233 HTMLFilePath= String(ma,tmpOutputDirectory);
234 }
235
236 // add a tag-file
237 Path tagFileURL= "file://"; tagFileURL << HTMLFilePath;
238 TagFiles.push_back({String(ma,tmpTagFilePath), String(ma, tagFileURL)});
239
240 // resolve the html output folder depth
241 if (!tmpCreateSubDirs) // coming from CREATE_SUBDIRS
242 HtmlSubfolderDepth= 0; // coming from CREATE_SUBDIRS_LEVEL and is always set
243}
244
246 auto& p= app::Get<DXLApp>().cOut;
247 p->Add("Dumping contents of doxyfile");
248 p->PushIndent(2);
249 p->Add("ExcludeSymlinks: ", ExcludeSymlinks);
250 p->Add("RecursiveInputScan: ", RecursiveInputScan);
251 p->Add("Tagfiles:");
252 {
253 p->PushIndent(2);
254 for (const auto& it : TagFiles)
255 p->Add("{} {!ATab} {}\n", it.TagFilePath, "->", it.BaseURL);
256 p->PopIndent();
257 }
258 p->Add();
259 p->Add("InputPaths:");
260 {
261 p->PushIndent(2);
262 for (const auto& it : InputPaths)
263 p->Add("{}\n", it);
264 p->PopIndent();
265 }
266 p->Add();
267 p->Add("FilePatterns: " );
268 p->PushIndent(4);
269 p->Add(FilePatterns );
270 p->PopIndent();
271 p->Add();
272 p->Add("ExcludePaths:");
273 {
274 p->PushIndent(2);
275 for (const auto& it : ExcludePaths)
276 p->Add("{}\n", it);
277 p->PopIndent();
278 }
279 p->Add();
280 p->Add("ExcludePatterns:");
281 {
282 p->PushIndent(2);
283 for (const auto& it : ExcludePatterns)
284 p->Add("{}\n", it);
285 p->PopIndent();
286 }
287 p->PopIndent();
288
289 app::Get<DXLApp>().onSdOutput();
290}
291
292} //namespace [dxl]
293
#define ALIB_CALLER_NULLED
#define Lox_Info(...)
#define Lox_SetDomain(...)
constexpr const TChar * Terminate() const
void DbgDisableBufferReplacementWarning()
void SetLength(integer newLength)
constexpr integer Length() const
constexpr bool IsEmpty() const
TChar CharAtStart() const
constexpr bool IsNotNull() const
constexpr bool IsNotEmpty() const
integer IndexOf(const TString &needle, integer startIdx=0, integer endIdx=strings::MAX_LEN) const
TChar CharAtEnd() const
TString< TChar > Substring(integer regionStart, integer regionLength=MAX_LEN) const
bool Equals(const TString< TChar > &rhs) const
TSubstring & TrimStart(const TCString< TChar > &whiteSpaces=CStringConstantsTraits< TChar >::DefaultWhitespaces())
integer ConsumeChars(integer regionLength, TAString< TChar, TAllocator > &target, integer separatorWidth=0)
bool ConsumeDec(std::integral auto &result, TNumberFormat< TChar > *numberFormat=nullptr)
TSubstring & Trim(const TCString< TChar > &whiteSpaces=CStringConstantsTraits< TChar >::DefaultWhitespaces())
TString< TChar > ConsumeToken(TChar separator=',', lang::Inclusion includeSeparator=lang::Inclusion::Include)
TSubstring & TrimEnd(const TCString< TChar > &whiteSpaces=CStringConstantsTraits< TChar >::DefaultWhitespaces())
static int IsAbsolute(const PathString &path)
class DXLApp
Definition dxlapp.hpp:37
TApp & Get()
strings::TString< PathCharType > PathString
constexpr PathCharType DIRECTORY_SEPARATOR
monomem::TMonoAllocator< lang::HeapAllocator > MonoAllocator
strings::compatibility::std::IStreamReader IStreamReader
containers::List< T, MonoAllocator, TRecycling > ListMA
lang::integer integer
LocalString< 4096 > String4K
strings::TString< character > String
system::Path Path
strings::TSubstring< character > Substring
characters::nchar nchar
exceptions::Exception Exception
LocalString< 512 > String512
todox
Definition doxyfile.cpp:20
@ CantOpenDoxyfile
Doxygen INI-file (usually Doxyfile) not found.
Definition dxl.hpp:89
@ NoTagfileGeneratedByDoxyfile
Definition dxl.hpp:90
alib::system::PathString TagFilePath
Path to the tag-file.
Definition doxyfile.hpp:26
alib::ListMA< alib::system::PathString > ExcludePaths
Definition doxyfile.hpp:57
alib::String HtmlFileExtension
Definition doxyfile.hpp:41
alib::String FilePatterns
Definition doxyfile.hpp:53
alib::ListMA< TagFileInfo > TagFiles
Definition doxyfile.hpp:67
unsigned HtmlSubfolderDepth
Definition doxyfile.hpp:45
alib::system::PathString HTMLFilePath
Definition doxyfile.hpp:37
alib::ListMA< alib::String > ExcludePatterns
Definition doxyfile.hpp:62
alib::ListMA< alib::system::PathString > InputPaths
Definition doxyfile.hpp:49
void Load(const alib::system::Path &filePath)
Definition doxyfile.cpp:22
void Dump() const
Writes the contents of this file to the console.
Definition doxyfile.cpp:245