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 auto addListTokensPath = [&](Substring src, ListMA<PathString>& listOut) {
105 while (src.IsNotNull()) {
106 src.TrimStart();
107 if (src.IsEmpty()) break;
108 if (src.CharAtStart() == '"') {
109 src.ConsumeChar('"');
110 integer end = src.IndexOf('"');
111 Substring tok;
112 if (end >= 0) { tok = src.Substring(0, end); src.ConsumeChars(end); src.ConsumeChar('"'); }
113 else { tok = src; src.ConsumeChars(src.Length()); }
114 tok.Trim();
115 if (tok.IsNotEmpty()) { PathString p; p= tok; listOut.emplace_back(String(ma,p)); }
116 } else {
117 Substring tok = src.ConsumeToken(' ');
118 tok.Trim();
119 if (tok.IsNotEmpty()) { PathString p; p= tok; listOut.emplace_back(String(ma,p)); }
120 } }
121 };
122
123 // Dispatch on key
124 if (key.Equals("OUTPUT_DIRECTORY")) {
125 stripQuotes(val);
126 tmpOutputDirectory = val;
127 }
128 else if (key.Equals("HTML_OUTPUT")) {
129 stripQuotes(val);
130 // Temporarily store raw value in HtmlOutput; resolve after loop using outputDirectory
131 tmpHtmlOutput = val;
132 }
133 else if (key.Equals("HTML_FILE_EXTENSION")) { stripQuotes(val); HtmlFileExtension = String(ma,val); }
134 else if (key.Equals("CREATE_SUBDIRS")) { tmpCreateSubDirs = val.Equals<CHK,lang::Case::Ignore>("YES"); }
135 else if (key.Equals("CREATE_SUBDIRS_LEVEL")){ val.ConsumeDec(HtmlSubfolderDepth); }
136 else if (key.Equals("INPUT")) { addListTokensPath(val, InputPaths); }
137 else if (key.Equals("RECURSIVE")) { RecursiveInputScan= val.Equals<CHK,lang::Case::Ignore>("YES"); }
138 else if (key.Equals("EXCLUDE")) { addListTokensPath(val, ExcludePaths); }
139 else if (key.Equals("EXCLUDE_SYMLINKS")) { ExcludeSymlinks = val.Equals<CHK,lang::Case::Ignore>("YES"); }
140 else if (key.Equals("EXCLUDE_PATTERNS")) { addListTokensStr (val, ExcludePatterns); }
141 else if (key.Equals("FILE_PATTERNS")) {
142 String512 buf;
143 while ( val.IsNotEmpty() ) {
144 val.TrimStart("* \t\r");
145 buf << val.ConsumeToken(' ') << ' ';
146 }
147 FilePatterns= String(ma, buf);
148 }
149 else if (key.Equals("TAGFILES")) {
150 // tokens of form file or file=loc (file/location may be quoted)
151 Substring rest(val);
152 while (rest.TrimStart().IsNotEmpty()) {
153 Substring token;
154 if (rest.CharAtStart() == '"') {
155 rest.ConsumeChar('"');
156 integer end = rest.IndexOf('"');
157 if (end >= 0) {
158 token = rest.Substring(0, end);
159 rest.ConsumeChars(end);
160 rest.ConsumeChar('"');
161 } else {
162 token = rest; rest.ConsumeChars(rest.Length());
163 }
164 } else {
165 token = rest.ConsumeToken();
166 }
167 token.Trim();
168 if (token.IsEmpty()) continue;
169
170 // split at '=' if present
171 integer eq = token.IndexOf('=');
172 TagFileInfo tfInfo;
173 if (eq >= 0) {
174 Substring f = token.Substring(0, eq); f.Trim();
175 Substring l = token.Substring(eq+1); l.Trim();
176 tfInfo.TagFilePath = String(ma,f);
177 if( l.CharAtEnd() != '/') {
178 Path lPlusSlash(l);
179 lPlusSlash << '/';
180 tfInfo.BaseURL = String(ma, lPlusSlash);
181 } else
182 tfInfo.BaseURL = String(ma,l);
183 } else {
184 tfInfo.TagFilePath = String(ma,token);
185 }
186 TagFiles.emplace_back(tfInfo);
187 } }
188 else if (key.Equals("GENERATE_TAGFILE")) {
189 stripQuotes(val);
190 if (val.IsNotEmpty()) {
191 tmpTagFilePath= val;
192 } }
193 };
194
195 while( !reader.IsEOF() ) {
196 reader.Read(lineBuf);
197
198 Substring line(lineBuf);
199 // append to accumulator
200 acc._(line);
201
202 // check for line continuation: trailing '\\' after trimming right spaces
203 Substring accView(acc);
204 accView.TrimEnd();
205 bool cont = accView.IsNotEmpty() && accView.CharAtEnd() == '\\';
206 if (cont) {
207 // remove trailing backslash and continue accumulating
208 acc.SetLength(acc.Length()-1);
209 acc._(' ');
210 continue;
211 }
212
213 // process logical line in acc
214 Substring logical(acc);
215 finalizeLogicalLine(logical);
216 acc.Reset();
217 }
218
219 if (tmpTagFilePath.IsEmpty()) {
220 app::Get<DXLApp>().cErr->Add(app::Get<DXLApp>().cli.ExitCodeDecls.Find(ExitCodes::NoTagfileGeneratedByDoxyfile).Mapped()->FormatString(),
221 filePath);
223 throw Exception( ALIB_CALLER_NULLED, app::App::Exceptions::ControlledEarlyExit );
224 }
225
226 // Resolve the HTML output directory relative to OUTPUT_DIRECTORY if not absolute
227 if (tmpHtmlOutput.IsAbsolute())
228 HTMLFilePath= String(ma,tmpHtmlOutput);
229 else {
230 tmpOutputDirectory << DIRECTORY_SEPARATOR << tmpHtmlOutput;
231 HTMLFilePath= String(ma,tmpOutputDirectory);
232 }
233
234 // add a tag-file
235 Path tagFileURL= ""; // just empty
236 TagFiles.push_back({String(ma,tmpTagFilePath), String(ma, tagFileURL)});
237
238 // resolve the html output folder depth
239 if (!tmpCreateSubDirs) // coming from CREATE_SUBDIRS
240 HtmlSubfolderDepth= 0; // coming from CREATE_SUBDIRS_LEVEL and is always set
241}
242
244 auto& p= app::Get<DXLApp>().cOut;
245 p->Add("Dumping contents of doxyfile");
246 p->PushIndent(2);
247 p->Add("ExcludeSymlinks: ", ExcludeSymlinks);
248 p->Add("RecursiveInputScan: ", RecursiveInputScan);
249 p->Add("Tagfiles:");
250 {
251 p->PushIndent(2);
252 for (const auto& it : TagFiles)
253 p->Add("{} {!ATab} {}\n", it.TagFilePath, "->", it.BaseURL);
254 p->PopIndent();
255 }
256 p->Add();
257 p->Add("InputPaths:");
258 {
259 p->PushIndent(2);
260 for (const auto& it : InputPaths)
261 p->Add("{}\n", it);
262 p->PopIndent();
263 }
264 p->Add();
265 p->Add("FilePatterns: " );
266 p->PushIndent(4);
267 p->Add(FilePatterns );
268 p->PopIndent();
269 p->Add();
270 p->Add("ExcludePaths:");
271 {
272 p->PushIndent(2);
273 for (const auto& it : ExcludePaths)
274 p->Add("{}\n", it);
275 p->PopIndent();
276 }
277 p->Add();
278 p->Add("ExcludePatterns:");
279 {
280 p->PushIndent(2);
281 for (const auto& it : ExcludePatterns)
282 p->Add("{}\n", it);
283 p->PopIndent();
284 }
285 p->PopIndent();
286
287 app::Get<DXLApp>().onSdOutput();
288}
289
290} //namespace [dxl]
#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 bool IsAbsolute(const PathString &path)
class DXLApp
Definition dxlapp.hpp:37
TApp & Get()
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
strings::TString< PathCharType > PathString
exceptions::Exception Exception
constexpr PathCharType DIRECTORY_SEPARATOR
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::PathString TagFilePath
Path to the tag-file.
Definition doxyfile.hpp:26
alib::String HtmlFileExtension
Definition doxyfile.hpp:41
alib::String FilePatterns
Definition doxyfile.hpp:53
alib::ListMA< TagFileInfo > TagFiles
Definition doxyfile.hpp:67
alib::PathString HTMLFilePath
Definition doxyfile.hpp:37
unsigned HtmlSubfolderDepth
Definition doxyfile.hpp:45
alib::ListMA< alib::String > ExcludePatterns
Definition doxyfile.hpp:62
alib::ListMA< alib::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:243
alib::ListMA< alib::PathString > ExcludePaths
Definition doxyfile.hpp:57