Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
config.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "config.h"
6#include "utilities.h"
7
8#include <QtCore/qdir.h>
9#include <QtCore/qfile.h>
10#include <QtCore/qfileinfo.h>
11#include <QtCore/qlatin1stringview.h>
12#include <QtCore/qtemporaryfile.h>
13#include <QtCore/qtextstream.h>
14#include <QtCore/qvariant.h>
15#include <QtCore/qregularexpression.h>
16
18
19using namespace Qt::StringLiterals;
20
21QString ConfigStrings::AUTOLINKERRORS = QStringLiteral("autolinkerrors");
22QString ConfigStrings::BUILDVERSION = QStringLiteral("buildversion");
23QString ConfigStrings::CODEINDENT = QStringLiteral("codeindent");
24QString ConfigStrings::CODELANGUAGES = QStringLiteral("codelanguages");
25QString ConfigStrings::CODEPREFIX = QStringLiteral("codeprefix");
26QString ConfigStrings::CODESUFFIX = QStringLiteral("codesuffix");
27QString ConfigStrings::CPPCLASSESPAGE = QStringLiteral("cppclassespage");
28QString ConfigStrings::CPPCLASSESTITLE = QStringLiteral("cppclassestitle");
29QString ConfigStrings::DEFINES = QStringLiteral("defines");
30QString ConfigStrings::DEPENDS = QStringLiteral("depends");
31QString ConfigStrings::DESCRIPTION = QStringLiteral("description");
32QString ConfigStrings::DOCBOOKEXTENSIONS = QStringLiteral("usedocbookextensions");
33QString ConfigStrings::DOCUMENTATIONINHEADERS = QStringLiteral("documentationinheaders");
34QString ConfigStrings::ENDHEADER = QStringLiteral("endheader");
35QString ConfigStrings::EXAMPLEDIRS = QStringLiteral("exampledirs");
36QString ConfigStrings::EXAMPLES = QStringLiteral("examples");
37QString ConfigStrings::EXAMPLESINSTALLPATH = QStringLiteral("examplesinstallpath");
38QString ConfigStrings::EXCLUDEDIRS = QStringLiteral("excludedirs");
39QString ConfigStrings::EXCLUDEFILES = QStringLiteral("excludefiles");
40QString ConfigStrings::EXTRAIMAGES = QStringLiteral("extraimages");
41QString ConfigStrings::FALSEHOODS = QStringLiteral("falsehoods");
42QString ConfigStrings::FORMATTING = QStringLiteral("formatting");
43QString ConfigStrings::HEADERDIRS = QStringLiteral("headerdirs");
44QString ConfigStrings::HEADERS = QStringLiteral("headers");
45QString ConfigStrings::HEADERSCRIPTS = QStringLiteral("headerscripts");
46QString ConfigStrings::HEADERSTYLES = QStringLiteral("headerstyles");
47QString ConfigStrings::HOMEPAGE = QStringLiteral("homepage");
48QString ConfigStrings::HOMETITLE = QStringLiteral("hometitle");
49QString ConfigStrings::IGNOREDIRECTIVES = QStringLiteral("ignoredirectives");
50QString ConfigStrings::IGNORESINCE = QStringLiteral("ignoresince");
51QString ConfigStrings::IGNORETOKENS = QStringLiteral("ignoretokens");
52QString ConfigStrings::IGNOREWORDS = QStringLiteral("ignorewords");
53QString ConfigStrings::IMAGEDIRS = QStringLiteral("imagedirs");
54QString ConfigStrings::IMAGESOUTPUTDIR = QStringLiteral("imagesoutputdir");
55QString ConfigStrings::INCLUDEPATHS = QStringLiteral("includepaths");
56QString ConfigStrings::INCLUDEPRIVATE = QStringLiteral("includeprivate");
57QString ConfigStrings::INTERNALFILEPATTERNS = QStringLiteral("internalfilepatterns");
58QString ConfigStrings::INCLUSIVE = QStringLiteral("inclusive");
59QString ConfigStrings::INDEXES = QStringLiteral("indexes");
60QString ConfigStrings::LANDINGPAGE = QStringLiteral("landingpage");
61QString ConfigStrings::LANDINGTITLE = QStringLiteral("landingtitle");
62QString ConfigStrings::LANGUAGE = QStringLiteral("language");
63QString ConfigStrings::LOCATIONINFO = QStringLiteral("locationinfo");
64QString ConfigStrings::LOGPROGRESS = QStringLiteral("logprogress");
65QString ConfigStrings::LOGWARNINGS = QStringLiteral("logwarnings");
66QString ConfigStrings::LOGWARNINGSDISABLECLIARGS = QStringLiteral("logwarnings.disablecliargs");
67QString ConfigStrings::MACRO = QStringLiteral("macro");
68QString ConfigStrings::MANIFESTMETA = QStringLiteral("manifestmeta");
69QString ConfigStrings::MODULEHEADER = QStringLiteral("moduleheader");
70QString ConfigStrings::NATURALLANGUAGE = QStringLiteral("naturallanguage");
71QString ConfigStrings::NAVIGATION = QStringLiteral("navigation");
72QString ConfigStrings::NOLINKERRORS = QStringLiteral("nolinkerrors");
73QString ConfigStrings::OUTPUTDIR = QStringLiteral("outputdir");
74QString ConfigStrings::OUTPUTFORMATS = QStringLiteral("outputformats");
75QString ConfigStrings::OUTPUTPREFIXES = QStringLiteral("outputprefixes");
76QString ConfigStrings::OUTPUTSUFFIXES = QStringLiteral("outputsuffixes");
77QString ConfigStrings::PRELIMINARY = QStringLiteral("preliminary");
78QString ConfigStrings::PRODUCTNAME = QStringLiteral("productname");
79QString ConfigStrings::PROJECT = QStringLiteral("project");
80QString ConfigStrings::PROJECTROOT = QStringLiteral("projectroot");
82 QStringLiteral("redirectdocumentationtodevnull");
84 QStringLiteral("reportmissingalttextforimages");
85QString ConfigStrings::QHP = QStringLiteral("qhp");
86QString ConfigStrings::QUOTINGINFORMATION = QStringLiteral("quotinginformation");
87QString ConfigStrings::ROOTDIR = QStringLiteral("rootdir");
88QString ConfigStrings::SCRIPTS = QStringLiteral("scripts");
89QString ConfigStrings::SHOWAUTOGENERATEDDOCS = QStringLiteral("showautogenerateddocs");
90QString ConfigStrings::SHOWINTERNAL = QStringLiteral("showinternal");
91QString ConfigStrings::SINGLEEXEC = QStringLiteral("singleexec");
92QString ConfigStrings::SOURCEDIRS = QStringLiteral("sourcedirs");
93QString ConfigStrings::SOURCEENCODING = QStringLiteral("sourceencoding");
94QString ConfigStrings::SOURCES = QStringLiteral("sources");
95QString ConfigStrings::SPURIOUS = QStringLiteral("spurious");
96QString ConfigStrings::STYLESHEETS = QStringLiteral("stylesheets");
97QString ConfigStrings::SYNTAXHIGHLIGHTING = QStringLiteral("syntaxhighlighting");
98QString ConfigStrings::TABSIZE = QStringLiteral("tabsize");
99QString ConfigStrings::TAGFILE = QStringLiteral("tagfile");
100QString ConfigStrings::TIMESTAMPS = QStringLiteral("timestamps");
101QString ConfigStrings::TOCTITLES = QStringLiteral("toctitles");
102QString ConfigStrings::TRADEMARKSPAGE = QStringLiteral("trademarkspage");
103QString ConfigStrings::URL = QStringLiteral("url");
104QString ConfigStrings::USEALTTEXTASTITLE = QStringLiteral("usealttextastitle");
105QString ConfigStrings::VERSION = QStringLiteral("version");
106QString ConfigStrings::VERSIONSYM = QStringLiteral("versionsym");
107QString ConfigStrings::FILEEXTENSIONS = QStringLiteral("fileextensions");
108QString ConfigStrings::IMAGEEXTENSIONS = QStringLiteral("imageextensions");
109QString ConfigStrings::QMLTYPESPAGE = QStringLiteral("qmltypespage");
110QString ConfigStrings::QMLTYPESTITLE = QStringLiteral("qmltypestitle");
111QString ConfigStrings::WARNABOUTMISSINGIMAGES = QStringLiteral("warnaboutmissingimages");
112QString ConfigStrings::WARNABOUTMISSINGPROJECTFILES = QStringLiteral("warnaboutmissingprojectfiles");
113QString ConfigStrings::WARNINGLIMIT = QStringLiteral("warninglimit");
114
115/*!
116 An entry in a stack, where each entry is a list
117 of string values.
118 */
120{
121public:
122 void open();
123 void close();
124
127};
129
130/*!
131 Start accumulating values in a list by appending an empty
132 string to the list.
133 */
135{
136 next.append(QString());
137}
138
139/*!
140 Stop accumulating values and append the list of accumulated
141 values to the complete list of accumulated values.
142
143 */
145{
146 accum += next;
147 next.clear();
148}
149
150/*!
151 \class MetaStack
152
153 This class maintains a stack of values of config file variables.
154*/
156{
157public:
159
160 void process(QChar ch, const Location &location);
162};
163
164/*!
165 The default constructor pushes a new stack entry and
166 opens it.
167 */
169{
170 push(MetaStackEntry());
171 top().open();
172}
173
174/*!
175 Processes the character \a ch using the \a location.
176 It really just builds up a name by appending \a ch to
177 it.
178 */
179void MetaStack::process(QChar ch, const Location &location)
180{
181 if (ch == QLatin1Char('{')) {
182 push(MetaStackEntry());
183 top().open();
184 } else if (ch == QLatin1Char('}')) {
185 if (size() == 1)
186 location.fatal(QStringLiteral("Unexpected '}'"));
187
188 top().close();
189 const QStringList suffixes = pop().accum;
190 const QStringList prefixes = top().next;
191
192 top().next.clear();
193 for (const auto &prefix : prefixes) {
194 for (const auto &suffix : suffixes)
195 top().next << prefix + suffix;
196 }
197 } else if (ch == QLatin1Char(',') && size() > 1) {
198 top().close();
199 top().open();
200 } else {
201 for (QString &topNext : top().next)
202 topNext += ch;
203 }
204}
205
206/*!
207 Returns the accumulated string values.
208 */
210{
211 if (size() > 1)
212 location.fatal(QStringLiteral("Missing '}'"));
213
214 top().close();
215 return top().accum;
216}
217
218const QString Config::dot = QLatin1String(".");
219bool Config::m_debug = false;
220bool Config::m_atomsDump = false;
221bool Config::generateExamples = true;
225QMap<QString, QString> Config::m_extractedDirs;
226QStack<QString> Config::m_workingDirs;
227QMap<QString, QStringList> Config::m_includeFilesMap;
228
229/*!
230 \class ConfigVar
231 \brief contains all the information for a single config variable in a
232 .qdocconf file.
233*/
234
235/*!
236 Returns this configuration variable as a string.
237
238 If the variable is not defined, returns \a defaultString.
239
240 \note By default, \a defaultString is a null string.
241 This allows determining whether a configuration variable is
242 undefined (returns a null string) or defined as empty
243 (returns a non-null, empty string).
244*/
245QString ConfigVar::asString(const QString defaultString) const
246{
247 if (m_name.isEmpty())
248 return defaultString;
249
250 QString result(""); // an empty but non-null string
251 for (const auto &value : std::as_const(m_values)) {
252 if (!result.isEmpty() && !result.endsWith(QChar('\n')))
253 result.append(QChar(' '));
254 result.append(value.m_value);
255 }
256 return result;
257}
258
259/*!
260 Returns this config variable as a string list.
261*/
263{
264 QStringList result;
265 for (const auto &value : std::as_const(m_values))
266 result << value.m_value;
267 return result;
268}
269
270/*!
271 Returns this config variable as a string set.
272*/
274{
275 const auto &stringList = asStringList();
276 return QSet<QString>(stringList.cbegin(), stringList.cend());
277}
278
279/*!
280 Returns this config variable as a boolean.
281*/
282bool ConfigVar::asBool() const
283{
284 return QVariant(asString()).toBool();
285}
286
287/*!
288 Returns this configuration variable as an integer; iterates
289 through the string list, interpreting each
290 string in the list as an integer and adding it to a total sum.
291
292 Returns 0 if this variable is defined as empty, and
293 -1 if it's is not defined.
294 */
295int ConfigVar::asInt() const
296{
297 const QStringList strs = asStringList();
298 if (strs.isEmpty())
299 return -1;
300
301 int sum = 0;
302 for (const auto &str : strs)
303 sum += str.toInt();
304 return sum;
305}
306
307/*!
308 Appends values to this ConfigVar, and adjusts the ExpandVar
309 parameters so that they continue to refer to the correct values.
310*/
311void ConfigVar::append(const ConfigVar &other)
312{
313 m_expandVars << other.m_expandVars;
314 QList<ExpandVar>::Iterator it = m_expandVars.end();
315 it -= other.m_expandVars.size();
316 std::for_each(it, m_expandVars.end(), [this](ExpandVar &v) {
317 v.m_valueIndex += m_values.size();
318 });
319 m_values << other.m_values;
320 m_location = other.m_location;
321}
322
323/*!
324 \class Config
325 \brief The Config class contains the configuration variables
326 for controlling how qdoc produces documentation.
327
328 Its load() function reads, parses, and processes a qdocconf file.
329 */
330
331/*!
332 \enum Config::PathFlags
333
334 Flags used for retrieving canonicalized paths from Config.
335
336 \value Validate
337 Issue a warning for paths that do not exist and
338 remove them from the returned list.
339
340 \value IncludePaths
341 Assume the variable contains include paths with
342 prefixes such as \c{-I} that are to be removed
343 before canonicalizing and then re-inserted.
344
345 \omitvalue None
346
347 \sa getCanonicalPathList()
348*/
349
350/*!
351 Initializes the Config with \a programName and sets all
352 internal state variables to either default values or to ones
353 defined in command line arguments \a args.
354 */
355void Config::init(const QString &programName, const QStringList &args)
356{
357 m_prog = programName;
358 processCommandLineOptions(args);
359 reset();
360}
361
363{
364 clear();
365}
366
367/*!
368 Clears the location and internal maps for config variables.
369 */
371{
372 m_location = Location();
373 m_configVars.clear();
374 m_includeFilesMap.clear();
375 m_excludedPaths.reset();
376 m_sourceLink.reset();
377 m_internalFilePatterns.reset();
378}
379
380/*!
381 Resets the Config instance and loads the default values. A subset
382 of command line options are published as configuration variables.
383 */
385{
386 clear();
387
388 // Load default values
389 load(Location(), u":/qdoc/config/defaults.qdocconf"_s);
390
391 // Publish options from the command line as config variables
392 const auto setListFlag = [this](const QString &key, bool test) {
393 setStringList(key, QStringList(test ? QStringLiteral("true") : QStringLiteral("false")));
394 };
395#define SET(opt, test) setListFlag(opt, m_parser.isSet(m_parser.test))
396 SET(CONFIG_SYNTAXHIGHLIGHTING, highlightingOption);
397 SET(CONFIG_SHOWINTERNAL, showInternalOption);
398 SET(CONFIG_SINGLEEXEC, singleExecOption);
399 SET(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL, redirectDocumentationToDevNullOption);
400 SET(CONFIG_AUTOLINKERRORS, autoLinkErrorsOption);
401#undef SET
402 m_showInternal = m_configVars.value(CONFIG_SHOWINTERNAL).asBool();
403 setListFlag(CONFIG_NOLINKERRORS,
404 m_parser.isSet(m_parser.noLinkErrorsOption)
405 || qEnvironmentVariableIsSet("QDOC_NOLINKERRORS"));
406
407 // CONFIG_DEFINES and CONFIG_INCLUDEPATHS are set in load()
408}
409
410/*!
411 Loads and parses the qdoc configuration file \a fileName.
412 If a previous project was loaded, this function first resets the
413 Config instance. Then it calls the other load() function, which
414 does the loading, parsing, and processing of the configuration file.
415 */
416void Config::load(const QString &fileName)
417{
418 // Reset if a previous project was loaded
419 if (m_configVars.contains(CONFIG_PROJECT))
420 reset();
421
422 load(Location(), fileName);
423 if (m_location.isEmpty())
424 m_location = Location(fileName);
425 else
426 m_location.setEtc(true);
427
428 // Resolve variables that are interpreted as paths
429 QString varName{CONFIG_URL + dot + CONFIG_SOURCES + dot + CONFIG_ROOTDIR};
430 setStringList(varName, getCanonicalPathList(varName, Validate));
431
432 expandVariables();
433
434 // Add defines and includepaths from command line to their
435 // respective configuration variables. Values set here are
436 // always added to what's defined in configuration file.
437 insertStringList(CONFIG_DEFINES, m_defines);
438 insertStringList(CONFIG_INCLUDEPATHS, m_includePaths);
439
440 // Prefetch values that are used internally
441 m_exampleFiles = getCanonicalPathList(CONFIG_EXAMPLES);
442 m_exampleDirs = getCanonicalPathList(CONFIG_EXAMPLEDIRS);
443 m_reportMissingAltTextForImages =
444 m_configVars.value(CONFIG_REPORTMISSINGALTTEXTFORIMAGES).asBool();
445
446 if (!m_parser.isSet(m_parser.showInternalOption) && !qEnvironmentVariableIsSet("QDOC_SHOW_INTERNAL"))
447 m_showInternal = m_configVars.value(CONFIG_SHOWINTERNAL).asBool();
448}
449
450/*!
451 Expands other config variables referred to in all stored ConfigVars.
452*/
453void Config::expandVariables()
454{
455 for (auto &configVar : m_configVars) {
456 for (auto it = configVar.m_expandVars.crbegin(); it != configVar.m_expandVars.crend(); ++it) {
457 Q_ASSERT(it->m_valueIndex < configVar.m_values.size());
458 const QString &key = it->m_var;
459 const auto &refVar = m_configVars.value(key);
460 if (refVar.m_name.isEmpty()) {
461 configVar.m_location.fatal(
462 QStringLiteral("Environment or configuration variable '%1' undefined")
463 .arg(it->m_var));
464 } else if (!refVar.m_expandVars.empty()) {
465 configVar.m_location.fatal(
466 QStringLiteral("Nested variable expansion not allowed"),
467 QStringLiteral("When expanding '%1' at %2:%3")
468 .arg(refVar.m_name, refVar.m_location.filePath(),
469 QString::number(refVar.m_location.lineNo())));
470 }
471 QString expanded;
472 if (it->m_delim.isNull())
473 expanded = m_configVars.value(key).asStringList().join(QString());
474 else
475 expanded = m_configVars.value(key).asStringList().join(it->m_delim);
476 configVar.m_values[it->m_valueIndex].m_value.insert(it->m_index, expanded);
477 }
478 configVar.m_expandVars.clear();
479 }
480}
481
482/*!
483 Sets the \a values of a configuration variable \a var from a string list.
484 */
485void Config::setStringList(const QString &var, const QStringList &values)
486{
487 m_configVars.insert(var, ConfigVar(var, values, QDir::currentPath()));
488}
489
490/*!
491 Adds the \a values from a string list to the configuration variable \a var.
492 Existing value(s) are kept.
493*/
494void Config::insertStringList(const QString &var, const QStringList &values)
495{
496 m_configVars[var].append(ConfigVar(var, values, QDir::currentPath()));
497}
498
499/*!
500 Process and store variables from the command line.
501 */
502void Config::processCommandLineOptions(const QStringList &args)
503{
504 m_parser.process(args);
505
506 m_defines = m_parser.values(m_parser.defineOption);
507 m_dependModules = m_parser.values(m_parser.dependsOption);
508 setIndexDirs();
509 setIncludePaths();
510
511 generateExamples = !m_parser.isSet(m_parser.noExamplesOption);
512 if (m_parser.isSet(m_parser.installDirOption))
513 installDir = m_parser.value(m_parser.installDirOption);
514 if (m_parser.isSet(m_parser.outputDirOption))
515 overrideOutputDir = QDir(m_parser.value(m_parser.outputDirOption)).absolutePath();
516
517 const auto outputFormats = m_parser.values(m_parser.outputFormatOption);
518 for (const auto &format : outputFormats)
519 overrideOutputFormats.insert(format);
520 m_debug = m_parser.isSet(m_parser.debugOption) || qEnvironmentVariableIsSet("QDOC_DEBUG");
521 m_atomsDump = m_parser.isSet(m_parser.atomsDumpOption);
522 m_showInternal = m_parser.isSet(m_parser.showInternalOption)
523 || qEnvironmentVariableIsSet("QDOC_SHOW_INTERNAL");
524
525 if (m_parser.isSet(m_parser.prepareOption))
526 m_qdocPass = Prepare;
527 if (m_parser.isSet(m_parser.generateOption))
528 m_qdocPass = Generate;
529 if (m_debug || m_parser.isSet(m_parser.logProgressOption))
530 setStringList(CONFIG_LOGPROGRESS, QStringList("true"));
531 if (m_parser.isSet(m_parser.timestampsOption))
532 setStringList(CONFIG_TIMESTAMPS, QStringList("true"));
533 if (m_parser.isSet(m_parser.useDocBookExtensions))
534 setStringList(CONFIG_DOCBOOKEXTENSIONS, QStringList("true"));
535}
536
537void Config::setIncludePaths()
538{
539 QDir currentDir = QDir::current();
540 const auto addIncludePaths = [this, currentDir](const char *flag, const QStringList &paths) {
541 for (const auto &path : paths)
542 m_includePaths << currentDir.absoluteFilePath(path).insert(0, flag);
543 };
544
545 addIncludePaths("-I", m_parser.values(m_parser.includePathOption));
546#ifdef QDOC_PASS_ISYSTEM
547 addIncludePaths("-isystem", m_parser.values(m_parser.includePathSystemOption));
548#endif
549 addIncludePaths("-F", m_parser.values(m_parser.frameworkOption));
550}
551
552/*!
553 Stores paths from --indexdir command line option(s).
554 */
555void Config::setIndexDirs()
556{
557 m_indexDirs = m_parser.values(m_parser.indexDirOption);
558 auto it = std::remove_if(m_indexDirs.begin(), m_indexDirs.end(),
559 [](const QString &s) { return !QFile::exists(s); });
560
561 std::for_each(it, m_indexDirs.end(), [](const QString &s) {
562 qCWarning(lcQdoc) << "Cannot find index directory: " << s;
563 });
564 m_indexDirs.erase(it, m_indexDirs.end());
565}
566
567/*!
568 Function to return the correct outputdir for the output \a format.
569 If \a format is not specified, defaults to 'HTML'.
570 outputdir can be set using the qdocconf or the command-line
571 variable --outputdir.
572 */
573QString Config::getOutputDir(const QString &format) const
574{
575 QString t;
576 if (overrideOutputDir.isNull())
577 t = m_configVars.value(CONFIG_OUTPUTDIR).asString();
578 else
579 t = overrideOutputDir;
580 if (m_configVars.value(CONFIG_SINGLEEXEC).asBool()) {
581 QString project = m_configVars.value(CONFIG_PROJECT).asString();
582 t += QLatin1Char('/') + project.toLower();
583 }
584 if (m_configVars.value(format + Config::dot + "nosubdirs").asBool()) {
585 QString singleOutputSubdir = m_configVars.value(format + Config::dot + "outputsubdir").asString();
586 if (singleOutputSubdir.isEmpty())
587 singleOutputSubdir = "html";
588 t += QLatin1Char('/') + singleOutputSubdir;
589 }
590 return QDir::cleanPath(t);
591}
592
593/*!
594 Function to return the correct outputformats.
595 outputformats can be set using the qdocconf or the command-line
596 variable --outputformat.
597 */
599{
600 if (overrideOutputFormats.isEmpty())
601 return m_configVars.value(CONFIG_OUTPUTFORMATS).asStringSet();
602 else
603 return overrideOutputFormats;
604}
605
606// TODO: [late-canonicalization][pod-configuration]
607// The canonicalization for paths is done at the time where they are
608// required, and done each time they are requested.
609// Instead, config should be parsed to an intermediate format that is
610// a POD type that already contains canonicalized representations for
611// each element.
612// Those representations should provide specific guarantees about
613// their format and be representable at the API boundaries.
614//
615// This would ensure that the correct canonicalization is always
616// applied, is applied only once and that dependent sub-logics can be
617// written in a way that doesn't require branching or futher
618// canonicalization.
619
620/*!
621 Returns a path list where all paths from the config variable \a var
622 are canonicalized. If \a flags contains \c Validate, outputs a warning
623 for invalid paths. The \c IncludePaths flag is used as a hint to strip
624 away potential prefixes found in include paths before attempting to
625 canonicalize.
626 */
627QStringList Config::getCanonicalPathList(const QString &var, PathFlags flags) const
628{
629 QStringList result;
630 const auto &configVar = m_configVars.value(var);
631
632 for (const auto &value : configVar.m_values) {
633 const QString &currentPath = value.m_path;
634 QString rawValue = value.m_value.simplified();
635 QString prefix;
636
637 if (flags & IncludePaths) {
638 const QStringList prefixes = QStringList()
639 << QLatin1String("-I")
640 << QLatin1String("-F")
641 << QLatin1String("-isystem");
642 const auto end = std::end(prefixes);
643 const auto it =
644 std::find_if(std::begin(prefixes), end,
645 [&rawValue](const QString &p) {
646 return rawValue.startsWith(p);
647 });
648 if (it != end) {
649 prefix = *it;
650 rawValue.remove(0, it->size());
651 if (rawValue.isEmpty())
652 continue;
653 } else {
654 prefix = prefixes[0]; // -I as default
655 }
656 }
657
658 QDir dir(rawValue.trimmed());
659 const QString path = dir.path();
660
661 if (dir.isRelative())
662 dir.setPath(currentPath + QLatin1Char('/') + path);
663 if ((flags & Validate) && !QFileInfo::exists(dir.path()))
664 configVar.m_location.warning(QStringLiteral("Cannot find file or directory: %1").arg(path));
665 else {
666 const QString canonicalPath = dir.canonicalPath();
667 if (!canonicalPath.isEmpty())
668 result.append(prefix + canonicalPath);
669 else if (path.contains(QLatin1Char('*')) || path.contains(QLatin1Char('?')))
670 result.append(path);
671 else
672 qCDebug(lcQdoc) <<
673 qUtf8Printable(QStringLiteral("%1: Ignored nonexistent path \'%2\'")
674 .arg(configVar.m_location.toString(), rawValue));
675 }
676 }
677 return result;
678}
679
680/*!
681 Calls getRegExpList() with the control variable \a var and
682 iterates through the resulting list of regular expressions,
683 concatenating them with extra characters to form a single
684 QRegularExpression, which is then returned.
685
686 \sa getRegExpList()
687 */
688QRegularExpression Config::getRegExp(const QString &var) const
689{
690 QString pattern;
691 const auto subRegExps = getRegExpList(var);
692
693 for (const auto &regExp : subRegExps) {
694 if (!regExp.isValid())
695 return regExp;
696 if (!pattern.isEmpty())
697 pattern += QLatin1Char('|');
698 pattern += QLatin1String("(?:") + regExp.pattern() + QLatin1Char(')');
699 }
700 if (pattern.isEmpty())
701 pattern = QLatin1String("$x"); // cannot match
702 return QRegularExpression(pattern);
703}
704
705/*!
706 Looks up the configuration variable \a var in the string list
707 map, converts the string list to a list of regular expressions,
708 and returns it.
709 */
710QList<QRegularExpression> Config::getRegExpList(const QString &var) const
711{
712 const QStringList strs = m_configVars.value(var).asStringList();
713 QList<QRegularExpression> regExps;
714 for (const auto &str : strs)
715 regExps += QRegularExpression(str);
716 return regExps;
717}
718
719/*!
720 This function is slower than it could be. What it does is
721 find all the keys that begin with \a var + dot and return
722 the matching keys in a set, stripped of the matching prefix
723 and dot.
724 */
725QSet<QString> Config::subVars(const QString &var) const
726{
727 QSet<QString> result;
728 QString varDot = var + QLatin1Char('.');
729 for (auto it = m_configVars.constBegin(); it != m_configVars.constEnd(); ++it) {
730 if (it.key().startsWith(varDot)) {
731 QString subVar = it.key().mid(varDot.size());
732 int dot = subVar.indexOf(QLatin1Char('.'));
733 if (dot != -1)
734 subVar.truncate(dot);
735 result.insert(subVar);
736 }
737 }
738 return result;
739}
740
741/*!
742 Searches for a path to \a fileName in 'sources', 'sourcedirs', and
743 'exampledirs' config variables and returns a full path to the first
744 match found. If the file is not found, returns an empty string.
745 */
746QString Config::getIncludeFilePath(const QString &fileName) const
747{
748 QString ext = QFileInfo(fileName).suffix();
749
750 if (!m_includeFilesMap.contains(ext)) {
751 QStringList result = getCanonicalPathList(CONFIG_SOURCES);
752 result.erase(std::remove_if(result.begin(), result.end(),
753 [&](const QString &s) { return !s.endsWith(ext); }),
754 result.end());
755 const QStringList dirs =
756 getCanonicalPathList(CONFIG_SOURCEDIRS) +
757 getCanonicalPathList(CONFIG_EXAMPLEDIRS);
758
759 for (const auto &dir : dirs)
760 result += getFilesHere(dir, "*." + ext, location());
761 result.removeDuplicates();
762 m_includeFilesMap.insert(ext, result);
763 }
764 const QStringList &paths = (*m_includeFilesMap.find(ext));
765 QString match = fileName;
766 if (!match.startsWith('/'))
767 match.prepend('/');
768 for (const auto &path : paths) {
769 if (path.endsWith(match))
770 return path;
771 }
772 return QString();
773}
774
775/*!
776 Builds and returns a list of file pathnames for the file
777 type specified by \a filesVar (e.g. "headers" or "sources").
778 The files are found in the directories specified by
779 \a dirsVar, and they are filtered by \a defaultNameFilter
780 if a better filter can't be constructed from \a filesVar.
781 The directories in \a excludedDirs are avoided. The files
782 in \a excludedFiles are not included in the return list.
783 */
784QStringList Config::getAllFiles(const QString &filesVar, const QString &dirsVar,
785 const QSet<QString> &excludedDirs,
786 const QSet<QString> &excludedFiles)
787{
788 QStringList result = getCanonicalPathList(filesVar, Validate);
789 const QStringList dirs = getCanonicalPathList(dirsVar, Validate);
790
791 const QString nameFilter = m_configVars.value(filesVar + dot + CONFIG_FILEEXTENSIONS).asString();
792
793 for (const auto &dir : dirs)
794 result += getFilesHere(dir, nameFilter, location(), excludedDirs, excludedFiles);
795 return result;
796}
797
798QStringList Config::getExampleQdocFiles(const QSet<QString> &excludedDirs,
799 const QSet<QString> &excludedFiles)
800{
801 QStringList result;
802 const QStringList dirs = getCanonicalPathList("exampledirs");
803 const QString nameFilter = " *.qdoc";
804
805 for (const auto &dir : dirs)
806 result += getFilesHere(dir, nameFilter, location(), excludedDirs, excludedFiles);
807 return result;
808}
809
810QStringList Config::getExampleImageFiles(const QSet<QString> &excludedDirs,
811 const QSet<QString> &excludedFiles)
812{
813 QStringList result;
814 const QStringList dirs = getCanonicalPathList("exampledirs");
815 const QString nameFilter = m_configVars.value(CONFIG_EXAMPLES + dot + CONFIG_IMAGEEXTENSIONS).asString();
816
817 for (const auto &dir : dirs)
818 result += getFilesHere(dir, nameFilter, location(), excludedDirs, excludedFiles);
819 return result;
820}
821
822// TODO: [misplaced-logic][examples][pod-configuration]
823// The definition of how an example is structured and how to find its
824// components should not be part of Config or, for that matter,
825// CppCodeParser, which is the actual caller of this method.
826// Move this method to a more appropriate place as soon as a suitable
827// place is available for it.
828
829/*!
830 Returns the path to the project file for \a examplePath, or an empty string
831 if no project file was found.
832 */
833QString Config::getExampleProjectFile(const QString &examplePath)
834{
835 QFileInfo fileInfo(examplePath);
836 QStringList validNames;
837 validNames << QLatin1String("CMakeLists.txt")
838 << fileInfo.fileName() + QLatin1String(".pro")
839 << fileInfo.fileName() + QLatin1String(".qmlproject")
840 << fileInfo.fileName() + QLatin1String(".pyproject")
841 << QLatin1String("qbuild.pro"); // legacy
842
843 QString projectFile;
844
845 for (const auto &name : std::as_const(validNames)) {
846 projectFile = Config::findFile(Location(), m_exampleFiles, m_exampleDirs,
847 examplePath + QLatin1Char('/') + name);
848 if (!projectFile.isEmpty())
849 return projectFile;
850 }
851
852 return projectFile;
853}
854
855// TODO: [pod-configuration]
856// Remove findFile completely from the configuration.
857// External usages of findFile were already removed but a last caller
858// of this method exists internally to Config in
859// `getExampleProjectFile`.
860// That method has to be removed at some point and this method should
861// go with it.
862// Do notice that FileResolver is the replacement for findFile but it
863// is designed, for now, with a scope that does only care about the
864// usages of findFile that are outside the Config class.
865// More specifically, it was designed to replace only the uses of
866// findFile that deal with user provided queries or queries related to
867// that.
868// The logic that is used internally in Config is the same, but has a
869// different conceptual meaning.
870// When findFile is permanently removed, it must be considered whether
871// FileResolver itself should be used for the same logic or not.
872
873/*!
874 \a fileName is the path of the file to find.
875
876 \a files and \a dirs are the lists where we must find the
877 components of \a fileName.
878
879 \a location is used for obtaining the file and line numbers
880 for report qdoc errors.
881 */
882QString Config::findFile(const Location &location, const QStringList &files,
883 const QStringList &dirs, const QString &fileName,
884 QString *userFriendlyFilePath)
885{
886 if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
887 if (userFriendlyFilePath)
888 *userFriendlyFilePath = fileName;
889 return fileName;
890 }
891
892 QFileInfo fileInfo;
893 QStringList components = fileName.split(QLatin1Char('?'));
894 QString firstComponent = components.first();
895
896 for (const auto &file : files) {
897 if (file == firstComponent || file.endsWith(QLatin1Char('/') + firstComponent)) {
898 fileInfo.setFile(file);
899 if (!fileInfo.exists())
900 location.fatal(QStringLiteral("File '%1' does not exist").arg(file));
901 break;
902 }
903 }
904
905 if (fileInfo.fileName().isEmpty()) {
906 for (const auto &dir : dirs) {
907 fileInfo.setFile(QDir(dir), firstComponent);
908 if (fileInfo.exists())
909 break;
910 }
911 }
912
913 if (userFriendlyFilePath)
914 userFriendlyFilePath->clear();
915 if (!fileInfo.exists())
916 return QString();
917
918 // <<REMARK: This is actually dead code. It is unclear what it tries
919 // to do and why but its usage is unnecessary in the current
920 // codebase.
921 // Indeed, the whole concept of the "userFriendlyFilePath" is
922 // removed for file searching.
923 // It will be removed directly with the whole of findFile, but it
924 // should not be considered anymore until then.
925 if (userFriendlyFilePath) {
926 for (auto c = components.constBegin();;) {
927 bool isArchive = (c != components.constEnd() - 1);
928 userFriendlyFilePath->append(*c);
929
930 if (isArchive) {
931 QString extracted = m_extractedDirs[fileInfo.filePath()];
932
933 ++c;
934 fileInfo.setFile(QDir(extracted), *c);
935 } else {
936 break;
937 }
938
939 userFriendlyFilePath->append(QLatin1Char('?'));
940 }
941 }
942 // REMARK>>
943
944 return fileInfo.filePath();
945}
946
947// TODO: [pod-configuration]
948// An intermediate representation for the configuration should only
949// contain data that will later be destructured into subsystem that
950// care about specific subsets of the configuration and can carry that
951// information with them, uniquely.
952// Remove copyFile, moving it into whatever will have the unique
953// resposability of knowing how to build an output directory for a
954// QDoc execution.
955// Should copy file being used for not only copying file to the build
956// output directory, split its responsabilities into smaller elements
957// instead of forcing the logic together.
958
959/*!
960 Copies the \a sourceFilePath to the file name constructed by
961 concatenating \a targetDirPath and the file name from the
962 \a userFriendlySourceFilePath. \a location is for identifying
963 the file and line number where a qdoc error occurred. The
964 constructed output file name is returned.
965 */
966QString Config::copyFile(const Location &location, const QString &sourceFilePath,
967 const QString &userFriendlySourceFilePath, const QString &targetDirPath)
968{
969 // TODO: A copying operation should only be performed on files
970 // that we assume to be available. Ensure that this is true at the
971 // API boundary and bubble up the error checking and reporting to
972 // call-site users. Possibly this will be as simple as
973 // ResolvedFile, but could not be done at the time of the introduction of
974 // that type as we first need to encapsulate the logic for
975 // copying files into an appropriate subsystem and have a better
976 // understanding of call-site usages.
977
978 QFile inFile(sourceFilePath);
979 if (!inFile.open(QFile::ReadOnly)) {
980 location.warning(QStringLiteral("Cannot open input file for copy: '%1': %2")
981 .arg(sourceFilePath, inFile.errorString()));
982 return QString();
983 }
984
985 // TODO: [non-canonical-representation]
986 // Similar to other part of QDoc, we do a series of non-intuitive
987 // checks to canonicalize some multi-format parameter into
988 // something we can use.
989 // Understand which of those formats are actually in use and
990 // provide a canonicalized version that can be requested at the
991 // API boundary to ensure that correct formatting is used.
992 // If possible, gradually bubble up the canonicalization until a
993 // single entry-point in the program exists where the
994 // canonicalization can be processed to avoid complicating
995 // intermediate steps.
996 // ADDENDUM 1: At least one usage of this seems to depend on the
997 // processing done for files coming from
998 // Generator::copyTemplateFile, which are expressed as absolute
999 // paths. This seems to be the only usage that is currently
1000 // needed, hence a temporary new implementation is provided that
1001 // only takes this case into account.
1002 // Do notice that we assume that in this case we always want a
1003 // flat structure, that is, we are copying the file as a direct
1004 // child of the target directory.
1005 // Nonetheless, it is possible that this case will not be needed,
1006 // such that it can be removed later on, or that it will be nedeed
1007 // in multiple places such that an higher level interface for it
1008 // should be provided.
1009 // Furthermoe, it might be possible that there is an edge case
1010 // that is now not considered, as it is unknown, that was
1011 // considered before.
1012 // As it is now unclear what kind of paths are used here, what
1013 // format they have, why they are used and why they have some
1014 // specific format, further processing is avoided but a more
1015 // torough overview of what should is needed must be done when
1016 // more information are gathered and this function is extracted
1017 // away from config.
1018
1019 QString outFileName{userFriendlySourceFilePath};
1020 QFileInfo outFileNameInfo{userFriendlySourceFilePath};
1021 if (outFileNameInfo.isAbsolute())
1022 outFileName = outFileNameInfo.fileName();
1023
1024 outFileName = targetDirPath + "/" + outFileName;
1025 QDir targetDir(targetDirPath);
1026 if (!targetDir.exists())
1027 targetDir.mkpath(".");
1028
1029 QFile outFile(outFileName);
1030 if (!outFile.open(QFile::WriteOnly)) {
1031 // TODO: [uncrentralized-warning]
1032 location.warning(QStringLiteral("Cannot open output file for copy: '%1': %2")
1033 .arg(outFileName, outFile.errorString()));
1034 return QString();
1035 }
1036
1037 // TODO: There shouldn't be any particular advantage to copying
1038 // the file by readying its content and writing it compared to
1039 // asking the underlying system to do the copy for us.
1040 // Consider simplifying this part by avoiding doing the manual
1041 // work ourselves.
1042
1043 char buffer[1024];
1044 qsizetype len;
1045 while ((len = inFile.read(buffer, sizeof(buffer))) > 0)
1046 outFile.write(buffer, len);
1047 return outFileName;
1048}
1049
1050/*!
1051 Finds the largest unicode digit in \a value in the range
1052 1..7 and returns it.
1053 */
1054int Config::numParams(const QString &value)
1055{
1056 int max = 0;
1057 for (int i = 0; i != value.size(); ++i) {
1058 uint c = value[i].unicode();
1059 if (c > 0 && c < 8)
1060 max = qMax(max, static_cast<int>(c));
1061 }
1062 return max;
1063}
1064
1065/*!
1066 Returns \c true if \a ch is a letter, number, '_', '.',
1067 '{', '}', or ','.
1068 */
1069bool Config::isMetaKeyChar(QChar ch)
1070{
1071 return ch.isLetterOrNumber() || ch == QLatin1Char('_') || ch == QLatin1Char('.')
1072 || ch == QLatin1Char('{') || ch == QLatin1Char('}') || ch == QLatin1Char(',');
1073}
1074
1075/*!
1076 \a fileName is a master qdocconf file. It contains a list of
1077 qdocconf files and nothing else. Read the list and return it.
1078 */
1079QStringList Config::loadMaster(const QString &fileName)
1080{
1081 Location location;
1082 QFile fin(fileName);
1083 if (!fin.open(QFile::ReadOnly | QFile::Text)) {
1084 if (!Config::installDir.isEmpty()) {
1085 qsizetype prefix = location.filePath().size() - location.fileName().size();
1086 fin.setFileName(Config::installDir + QLatin1Char('/')
1087 + fileName.right(fileName.size() - prefix));
1088 }
1089 if (!fin.open(QFile::ReadOnly | QFile::Text))
1090 location.fatal(QStringLiteral("Cannot open master qdocconf file '%1': %2")
1091 .arg(fileName, fin.errorString()));
1092 }
1093 QTextStream stream(&fin);
1094 QStringList qdocFiles;
1095 QDir configDir(QFileInfo(fileName).canonicalPath());
1096 QString line = stream.readLine();
1097 while (!line.isNull()) {
1098 if (!line.isEmpty())
1099 qdocFiles.append(QFileInfo(configDir, line).filePath());
1100 line = stream.readLine();
1101 }
1102 fin.close();
1103 return qdocFiles;
1104}
1105
1106/*!
1107 Load, parse, and process a QDoc configuration file \a fileName.
1108 This function is only called by the other load() function, but
1109 is recursive, i.e., it calls itself when it sees an \c include
1110 statement in the configuration file.
1111
1112 The path of the loaded configuration file is pushed to a stack
1113 and set as the current working directory, unless \a fileName
1114 comes from the resource system. Configuration files from
1115 resources can only include() other files with an absolute path,
1116 or \c {:/path/to/qdocconf}.
1117 */
1118void Config::load(Location location, const QString &fileName)
1119{
1120 QFileInfo fileInfo(fileName);
1121 const bool isResource = fileName.startsWith(u':');
1122 if (!isResource)
1123 pushWorkingDir(fileInfo.canonicalPath());
1124 static const QRegularExpression keySyntax(QRegularExpression::anchoredPattern(QLatin1String("\\w+(?:\\.\\w+)*")));
1125
1126#define SKIP_CHAR()
1127 do {
1128 location.advance(c);
1129 ++i;
1130 c = text.at(i);
1131 cc = c.unicode();
1132 } while (0)
1133
1134#define SKIP_SPACES()
1135 while (c.isSpace() && cc != '\n')
1136 SKIP_CHAR()
1137
1138#define PUT_CHAR()
1139 word += c;
1140 SKIP_CHAR();
1141
1142 if (location.depth() > 16)
1143 location.fatal(QStringLiteral("Too many nested includes"));
1144
1145 QFile fin(isResource ? fileInfo.filePath() : fileInfo.fileName());
1146 if (!fin.open(QFile::ReadOnly | QFile::Text)) {
1147 if (!Config::installDir.isEmpty()) {
1148 qsizetype prefix = location.filePath().size() - location.fileName().size();
1149 fin.setFileName(Config::installDir + QLatin1Char('/')
1150 + fileName.right(fileName.size() - prefix));
1151 }
1152 if (!fin.open(QFile::ReadOnly | QFile::Text))
1153 location.fatal(
1154 QStringLiteral("Cannot open file '%1': %2").arg(fileName, fin.errorString()));
1155 }
1156
1157 QTextStream stream(&fin);
1158 QString text = stream.readAll();
1159 text += QLatin1String("\n\n");
1160 text += QLatin1Char('\0');
1161 fin.close();
1162
1163 location.push(fileName);
1164 location.start();
1165
1166 int i = 0;
1167 QChar c = text.at(0);
1168 uint cc = c.unicode();
1169 while (i < text.size()) {
1170 if (cc == 0) {
1171 ++i;
1172 } else if (c.isSpace()) {
1173 SKIP_CHAR();
1174 } else if (cc == '#') {
1175 do {
1176 SKIP_CHAR();
1177 } while (cc != '\n');
1178 } else if (isMetaKeyChar(c)) {
1179 Location keyLoc = location;
1180 bool plus = false;
1181 QStringList rhsValues;
1182 QList<ExpandVar> expandVars;
1183 QString word;
1184 bool inQuote = false;
1185 bool needsExpansion = false;
1186
1187 MetaStack stack;
1188 do {
1189 stack.process(c, location);
1190 SKIP_CHAR();
1191 } while (isMetaKeyChar(c));
1192
1193 const QStringList keys = stack.getExpanded(location);
1194 SKIP_SPACES();
1195
1196 if (keys.size() == 1 && keys.first() == QLatin1String("include")) {
1197 QString includeFile;
1198
1199 if (cc != '(')
1200 location.fatal(QStringLiteral("Bad include syntax"));
1201 SKIP_CHAR();
1202 SKIP_SPACES();
1203
1204 while (!c.isSpace() && cc != '#' && cc != ')') {
1205
1206 if (cc == '$') {
1207 QString var;
1208 SKIP_CHAR();
1209 while (c.isLetterOrNumber() || cc == '_') {
1210 var += c;
1211 SKIP_CHAR();
1212 }
1213 if (!var.isEmpty()) {
1214 const QByteArray val = qgetenv(var.toLatin1().data());
1215 if (val.isNull()) {
1216 location.fatal(QStringLiteral("Environment variable '%1' undefined")
1217 .arg(var));
1218 } else {
1219 includeFile += QString::fromLatin1(val);
1220 }
1221 }
1222 } else {
1223 includeFile += c;
1224 SKIP_CHAR();
1225 }
1226 }
1227 SKIP_SPACES();
1228 if (cc != ')')
1229 location.fatal(QStringLiteral("Bad include syntax"));
1230 SKIP_CHAR();
1231 SKIP_SPACES();
1232 if (cc != '#' && cc != '\n')
1233 location.fatal(QStringLiteral("Trailing garbage"));
1234
1235 /*
1236 Here is the recursive call.
1237 */
1238 if (isResource)
1239 load(location, includeFile);
1240 else
1241 load(location, QFileInfo(QDir(m_workingDirs.top()), includeFile).filePath());
1242 } else {
1243 /*
1244 It wasn't an include statement, so it's something else.
1245 We must see either '=' or '+=' next. If not, fatal error.
1246 */
1247 if (cc == '+') {
1248 plus = true;
1249 SKIP_CHAR();
1250 }
1251 if (cc != '=')
1252 location.fatal(QStringLiteral("Expected '=' or '+=' after key"));
1253 SKIP_CHAR();
1254 SKIP_SPACES();
1255
1256 for (;;) {
1257 if (cc == '\\') {
1258 qsizetype metaCharPos;
1259
1260 SKIP_CHAR();
1261 if (cc == '\n') {
1262 SKIP_CHAR();
1263 } else if (cc > '0' && cc < '8') {
1264 word += QChar(c.digitValue());
1265 SKIP_CHAR();
1266 } else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c))
1267 != -1) {
1268 word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]);
1269 SKIP_CHAR();
1270 } else {
1271 PUT_CHAR();
1272 }
1273 } else if (c.isSpace() || cc == '#') {
1274 if (inQuote) {
1275 if (cc == '\n')
1276 location.fatal(QStringLiteral("Unterminated string"));
1277 PUT_CHAR();
1278 } else {
1279 if (!word.isEmpty() || needsExpansion) {
1280 rhsValues << word;
1281 word.clear();
1282 needsExpansion = false;
1283 }
1284 if (cc == '\n' || cc == '#')
1285 break;
1286 SKIP_SPACES();
1287 }
1288 } else if (cc == '"') {
1289 if (inQuote) {
1290 if (!word.isEmpty() || needsExpansion)
1291 rhsValues << word;
1292 word.clear();
1293 needsExpansion = false;
1294 }
1295 inQuote = !inQuote;
1296 SKIP_CHAR();
1297 } else if (cc == '$') {
1298 QString var;
1299 QChar delim(' ');
1300 bool braces = false;
1301 SKIP_CHAR();
1302 if (cc == '{') {
1303 SKIP_CHAR();
1304 braces = true;
1305 }
1306 while (c.isLetterOrNumber() || cc == '_') {
1307 var += c;
1308 SKIP_CHAR();
1309 }
1310 if (braces) {
1311 if (cc == ',') {
1312 SKIP_CHAR();
1313 delim = c;
1314 SKIP_CHAR();
1315 }
1316 if (cc == '}')
1317 SKIP_CHAR();
1318 else if (delim == '}')
1319 delim = QChar(); // null delimiter
1320 else
1321 location.fatal(QStringLiteral("Missing '}'"));
1322 }
1323 if (!var.isEmpty()) {
1324 const QByteArray val = qgetenv(var.toLatin1().constData());
1325 if (val.isNull()) {
1326 expandVars << ExpandVar(rhsValues.size(), word.size(), std::move(var), delim);
1327 needsExpansion = true;
1328 } else if (braces) { // ${VAR} inserts content from an env. variable for processing
1329 text.insert(i, QString::fromLatin1(val));
1330 c = text.at(i);
1331 cc = c.unicode();
1332 } else { // while $VAR simply reads the value and stores it to a config variable.
1333 word += QString::fromLatin1(val);
1334 }
1335 }
1336 } else {
1337 if (!inQuote && cc == '=')
1338 location.fatal(QStringLiteral("Unexpected '='"));
1339 PUT_CHAR();
1340 }
1341 }
1342 for (const auto &key : keys) {
1343 if (!keySyntax.match(key).hasMatch())
1344 keyLoc.fatal(QStringLiteral("Invalid key '%1'").arg(key));
1345
1346 ConfigVar configVar(key, rhsValues, QDir::currentPath(), keyLoc, expandVars);
1347 if (plus && m_configVars.contains(key)) {
1348 m_configVars[key].append(configVar);
1349 } else {
1350 m_configVars.insert(key, configVar);
1351 }
1352 }
1353 }
1354 } else {
1355 location.fatal(QStringLiteral("Unexpected character '%1' at beginning of line").arg(c));
1356 }
1357 }
1358 if (!isResource)
1360
1361#undef SKIP_CHAR
1362#undef SKIP_SPACES
1363#undef PUT_CHAR
1364}
1365
1366bool Config::isFileExcluded(const QString &fileName, const QSet<QString> &excludedFiles)
1367{
1368 for (const QString &entry : excludedFiles) {
1369 if (entry.contains(QLatin1Char('*')) || entry.contains(QLatin1Char('?'))) {
1370 QRegularExpression re(QRegularExpression::wildcardToRegularExpression(entry));
1371 if (re.match(fileName).hasMatch())
1372 return true;
1373 }
1374 }
1375 return excludedFiles.contains(fileName);
1376}
1377
1378QStringList Config::getFilesHere(const QString &uncleanDir, const QString &nameFilter,
1379 const Location &location, const QSet<QString> &excludedDirs,
1380 const QSet<QString> &excludedFiles)
1381{
1382 // TODO: Understand why location is used to branch the
1383 // canonicalization and why the two different methods are used.
1384 QString dir =
1385 location.isEmpty() ? QDir::cleanPath(uncleanDir) : QDir(uncleanDir).canonicalPath();
1386 QStringList result;
1387 if (excludedDirs.contains(dir))
1388 return result;
1389
1390 QDir dirInfo(dir);
1391
1392 dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' ')));
1393 dirInfo.setSorting(QDir::Name);
1394 dirInfo.setFilter(QDir::Files);
1395 QStringList fileNames = dirInfo.entryList();
1396 for (const auto &file : std::as_const(fileNames)) {
1397 // TODO: Understand if this is needed and, should it be, if it
1398 // is indeed the only case that should be considered.
1399 if (!file.startsWith(QLatin1Char('~'))) {
1400 QString s = dirInfo.filePath(file);
1401 QString c = QDir::cleanPath(s);
1402 if (!isFileExcluded(c, excludedFiles))
1403 result.append(c);
1404 }
1405 }
1406
1407 dirInfo.setNameFilters(QStringList(QLatin1String("*")));
1408 dirInfo.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
1409 fileNames = dirInfo.entryList();
1410 for (const auto &file : fileNames)
1411 result += getFilesHere(dirInfo.filePath(file), nameFilter, location, excludedDirs,
1412 excludedFiles);
1413 return result;
1414}
1415
1416/*!
1417 Set \a dir as the working directory and push it onto the
1418 stack of working directories.
1419 */
1420void Config::pushWorkingDir(const QString &dir)
1421{
1422 m_workingDirs.push(dir);
1423 QDir::setCurrent(dir);
1424}
1425
1426/*!
1427 Pop the top entry from the stack of working directories.
1428 Set the working directory to the next one on the stack,
1429 if one exists.
1430 */
1432{
1433 Q_ASSERT(!m_workingDirs.isEmpty());
1434 m_workingDirs.pop();
1435 if (!m_workingDirs.isEmpty())
1436 QDir::setCurrent(m_workingDirs.top());
1437}
1438
1440 if (m_excludedPaths)
1441 return *m_excludedPaths;
1442
1443 const auto &excludedDirList = getCanonicalPathList(CONFIG_EXCLUDEDIRS);
1444 const auto &excludedFilesList = getCanonicalPathList(CONFIG_EXCLUDEFILES);
1445
1446 QSet<QString> excludedDirs = QSet<QString>(excludedDirList.cbegin(), excludedDirList.cend());
1447 QSet<QString> excludedFiles = QSet<QString>(excludedFilesList.cbegin(), excludedFilesList.cend());
1448
1449 m_excludedPaths.emplace(ExcludedPaths{std::move(excludedDirs), std::move(excludedFiles)});
1450
1451 return *m_excludedPaths;
1452}
1453
1454/*!
1455 Returns the set of file patterns that identify internal implementation files.
1456 Classes declared in files matching these patterns will have their nodes marked
1457 as internal, excluding them and their members from documentation requirements
1458 when showInternal is false.
1459
1460 \sa isFileExcluded()
1461*/
1463{
1464 const QStringList patterns = get(CONFIG_INTERNALFILEPATTERNS).asStringList();
1465 return QSet<QString>(patterns.cbegin(), patterns.cend());
1466}
1467
1468/*!
1469 Returns a reference to the pre-compiled internal file patterns structure.
1470 This method caches the compiled patterns for efficient reuse across multiple
1471 file checks. The patterns are compiled once when first accessed and remain
1472 valid until the configuration is cleared or reloaded.
1473
1474 The returned structure separates patterns into:
1475 - exactMatches: Patterns without wildcards for direct string matching.
1476 - regexPatterns: Pre-compiled regular expressions for regular expression
1477 patterns.
1478
1479 \sa matchesInternalFilePattern(), clear()
1480*/
1482{
1483 if (m_internalFilePatterns)
1484 return *m_internalFilePatterns;
1485
1486 InternalFilePatterns compiled;
1487 const QStringList patterns = get(CONFIG_INTERNALFILEPATTERNS).asStringList();
1488
1489 for (const QString &pattern : patterns) {
1490 static const QRegularExpression regexMetaChars(
1491 uR"(\.\*|\.\+|\.\{|\\‍[\.\*\?]|[\^\$\‍[\‍]{}()|+])"_s);
1492 bool isRawRegex = regexMetaChars.match(pattern).hasMatch();
1493
1494 if (isRawRegex) {
1495 QRegularExpression re(pattern);
1496 if (!re.isValid()) {
1497 qWarning() << "Invalid regex pattern in internalfilepatterns:" << pattern
1498 << "-" << re.errorString();
1499 continue;
1500 }
1501 re.optimize();
1502 compiled.regexPatterns.append(re);
1503 } else if (pattern.contains(u'*') || pattern.contains(u'?')) {
1504 QString regexPattern = QRegularExpression::wildcardToRegularExpression(pattern);
1505 QRegularExpression re(regexPattern);
1506 re.optimize();
1507 compiled.globPatterns.append(re);
1508 } else {
1509 compiled.exactMatches.insert(pattern);
1510 }
1511 }
1512
1513 m_internalFilePatterns.emplace(std::move(compiled));
1514 return *m_internalFilePatterns;
1515}
1516
1517/*!
1518 Returns true if the given \a filePath matches any pattern in the
1519 \a patterns structure. This is an optimized version that uses pre-compiled
1520 regular expressions.
1521
1522 The function first checks for exact matches (patterns without wildcards),
1523 which compare against the full normalized path. Then it checks glob patterns
1524 against the filename only, and finally regex patterns against the full path.
1525
1526 Path normalization is handled internally: all backslashes are converted to
1527 forward slashes to ensure cross-platform consistency, as patterns are
1528 expected to use forward slashes.
1529
1530 \sa getInternalFilePatternsCompiled()
1531*/
1532bool Config::matchesInternalFilePattern(const QString &filePath,
1533 const InternalFilePatterns &patterns) noexcept
1534{
1535 if (patterns.exactMatches.isEmpty() && patterns.globPatterns.isEmpty()
1536 && patterns.regexPatterns.isEmpty())
1537 return false;
1538
1539 QString norm = filePath;
1540 norm.replace(QChar('\\'), QChar('/'));
1541
1542 if (patterns.exactMatches.contains(norm))
1543 return true;
1544
1545 const QString fileName = QFileInfo(norm).fileName();
1546 for (const QRegularExpression &re : patterns.globPatterns) {
1547 if (re.match(fileName).hasMatch())
1548 return true;
1549 }
1550
1551 for (const QRegularExpression &re : patterns.regexPatterns) {
1552 if (re.match(norm).hasMatch())
1553 return true;
1554 }
1555
1556 return false;
1557}
1558
1559/*!
1560 Returns a SourceLink struct with settings required to
1561 construct source links to API entities.
1562*/
1564{
1565 if (m_sourceLink)
1566 return *m_sourceLink;
1567
1568 const auto srcUrl{CONFIG_URL + Config::dot + CONFIG_SOURCES};
1569
1570 const auto baseUrl = m_configVars.value(srcUrl).asString();
1571 const auto rootPath = m_configVars.value(srcUrl + dot + CONFIG_ROOTDIR).asString();
1572 const auto linkText = m_configVars.value(srcUrl + dot + "linktext").asString();
1573 const auto enabled = m_configVars.value(srcUrl + dot + "enabled").asBool();
1574
1575 m_sourceLink.emplace(SourceLink{baseUrl, rootPath, linkText, enabled});
1576 return *m_sourceLink;
1577}
1578
1580 static QStringList accepted_header_file_extensions{
1581 "ch", "h", "h++", "hh", "hpp", "hxx"
1582 };
1583
1584 const auto& [excludedDirs, excludedFiles] = getExcludedPaths();
1585
1586 QStringList headerList =
1587 getAllFiles(CONFIG_HEADERS, CONFIG_HEADERDIRS, excludedDirs, excludedFiles);
1588
1589 std::set<HeaderFilePath> headers{};
1590
1591 for (const auto& header : headerList) {
1592 if (header.contains("doc/snippets")) continue;
1593
1594 if (!accepted_header_file_extensions.contains(QFileInfo{header}.suffix()))
1595 continue;
1596
1597 headers.insert(HeaderFilePath{QFileInfo{header}.canonicalPath(), QFileInfo{header}.fileName()});
1598 }
1599
1600 return headers;
1601}
1602
1604{
1605 return m_configVars.value(CONFIG_SHOWAUTOGENERATEDDOCS).asBool();
1606}
1607
1608InclusionPolicy Config::createInclusionPolicy() const
1609{
1610 InclusionPolicy policy;
1611
1612 policy.includePrivateFunction = includePrivateFunction();
1613 policy.includePrivateType = includePrivateType();
1614 policy.includePrivateVariable = includePrivateVariable();
1615 policy.showInternal = showInternal();
1616 policy.showAutoGeneratedDocs = showAutoGeneratedDocs();
1617
1618 return policy;
1619}
1620
1621QT_END_NAMESPACE
contains all the information for a single config variable in a .qdocconf file.
Definition config.h:43
QString asString(const QString defaultString=QString()) const
Returns this configuration variable as a string.
Definition config.cpp:245
QStringList asStringList() const
Returns this config variable as a string list.
Definition config.cpp:262
int asInt() const
Returns this configuration variable as an integer; iterates through the string list,...
Definition config.cpp:295
bool asBool() const
Returns this config variable as a boolean.
Definition config.cpp:282
QSet< QString > asStringSet() const
Returns this config variable as a string set.
Definition config.cpp:273
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:85
static QString installDir
Definition config.h:172
QString getOutputDir(const QString &format=QString("HTML")) const
Function to return the correct outputdir for the output format.
Definition config.cpp:573
std::set< HeaderFilePath > getHeaderFiles()
Definition config.cpp:1579
static bool generateExamples
Definition config.h:171
void insertStringList(const QString &var, const QStringList &values)
Adds the values from a string list to the configuration variable var.
Definition config.cpp:494
static const QString dot
Definition config.h:169
QStringList getAllFiles(const QString &filesVar, const QString &dirsVar, const QSet< QString > &excludedDirs=QSet< QString >(), const QSet< QString > &excludedFiles=QSet< QString >())
Builds and returns a list of file pathnames for the file type specified by filesVar (e....
Definition config.cpp:784
QSet< QString > getInternalFilePatterns() const
Returns the set of file patterns that identify internal implementation files.
Definition config.cpp:1462
void reset()
Resets the Config instance and loads the default values.
Definition config.cpp:384
InclusionPolicy createInclusionPolicy() const
Definition config.cpp:1608
void setStringList(const QString &var, const QStringList &values)
Sets the values of a configuration variable var from a string list.
Definition config.cpp:485
void clear()
Clears the location and internal maps for config variables.
Definition config.cpp:370
QSet< QString > subVars(const QString &var) const
This function is slower than it could be.
Definition config.cpp:725
~Config()
Definition config.cpp:362
bool includePrivateType() const
Definition config.h:480
PathFlags
Flags used for retrieving canonicalized paths from Config.
Definition config.h:91
bool includePrivateVariable() const
Definition config.h:486
const ExcludedPaths & getExcludedPaths()
Definition config.cpp:1439
QSet< QString > getOutputFormats() const
Function to return the correct outputformats.
Definition config.cpp:598
static void popWorkingDir()
Pop the top entry from the stack of working directories.
Definition config.cpp:1431
bool showAutoGeneratedDocs() const
Definition config.cpp:1603
@ Generate
Definition config.h:89
@ Prepare
Definition config.h:89
QRegularExpression getRegExp(const QString &var) const
Calls getRegExpList() with the control variable var and iterates through the resulting list of regula...
Definition config.cpp:688
QString getExampleProjectFile(const QString &examplePath)
Returns the path to the project file for examplePath, or an empty string if no project file was found...
Definition config.cpp:833
bool includePrivateFunction() const
Definition config.h:474
static QSet< QString > overrideOutputFormats
Definition config.h:174
static QString overrideOutputDir
Definition config.h:173
QStringList getCanonicalPathList(const QString &var, PathFlags flags=None) const
Returns a path list where all paths from the config variable var are canonicalized.
Definition config.cpp:627
void load(const QString &fileName)
Loads and parses the qdoc configuration file fileName.
Definition config.cpp:416
const SourceLink & getSourceLink()
Returns a SourceLink struct with settings required to construct source links to API entities.
Definition config.cpp:1563
bool showInternal() const
Definition config.h:111
QList< QRegularExpression > getRegExpList(const QString &var) const
Looks up the configuration variable var in the string list map, converts the string list to a list of...
Definition config.cpp:710
QStringList getExampleImageFiles(const QSet< QString > &excludedDirs, const QSet< QString > &excludedFiles)
Definition config.cpp:810
QStringList getExampleQdocFiles(const QSet< QString > &excludedDirs, const QSet< QString > &excludedFiles)
Definition config.cpp:798
const InternalFilePatterns & getInternalFilePatternsCompiled()
Returns a reference to the pre-compiled internal file patterns structure.
Definition config.cpp:1481
void init(const QString &programName, const QStringList &args)
Initializes the Config with programName and sets all internal state variables to either default value...
Definition config.cpp:355
QString getIncludeFilePath(const QString &fileName) const
Searches for a path to fileName in 'sources', 'sourcedirs', and 'exampledirs' config variables and re...
Definition config.cpp:746
The Location class provides a way to mark a location in a file.
Definition location.h:20
int depth() const
Definition location.h:46
void start()
If the file position on top of the stack has a line number less than 1, set its line number to 1 and ...
Definition location.cpp:134
An entry in a stack, where each entry is a list of string values.
Definition config.cpp:120
QStringList next
Definition config.cpp:126
void close()
Stop accumulating values and append the list of accumulated values to the complete list of accumulate...
Definition config.cpp:144
QStringList accum
Definition config.cpp:125
void open()
Start accumulating values in a list by appending an empty string to the list.
Definition config.cpp:134
This class maintains a stack of values of config file variables.
Definition config.cpp:156
QStringList getExpanded(const Location &location)
Returns the accumulated string values.
Definition config.cpp:209
void process(QChar ch, const Location &location)
Processes the character ch using the location.
Definition config.cpp:179
MetaStack()
The default constructor pushes a new stack entry and opens it.
Definition config.cpp:168
Q_DECLARE_TYPEINFO(MetaStackEntry, Q_RELOCATABLE_TYPE)
#define SKIP_CHAR()
#define PUT_CHAR()
#define SKIP_SPACES()
#define CONFIG_SOURCES
Definition config.h:438
#define CONFIG_REDIRECTDOCUMENTATIONTODEVNULL
Definition config.h:427
#define CONFIG_FILEEXTENSIONS
Definition config.h:451
#define CONFIG_OUTPUTDIR
Definition config.h:419
#define CONFIG_AUTOLINKERRORS
Definition config.h:367
#define CONFIG_SHOWINTERNAL
Definition config.h:434
#define CONFIG_SHOWAUTOGENERATEDDOCS
Definition config.h:433
#define CONFIG_DOCBOOKEXTENSIONS
Definition config.h:378
#define CONFIG_SINGLEEXEC
Definition config.h:435
#define CONFIG_EXAMPLES
Definition config.h:382
#define CONFIG_EXAMPLEDIRS
Definition config.h:381
#define CONFIG_URL
Definition config.h:447
#define CONFIG_SYNTAXHIGHLIGHTING
Definition config.h:441
#define CONFIG_DEFINES
Definition config.h:375
#define CONFIG_TIMESTAMPS
Definition config.h:444
#define CONFIG_NOLINKERRORS
Definition config.h:418
#define CONFIG_LOGPROGRESS
Definition config.h:410
#define CONFIG_PROJECT
Definition config.h:425
#define CONFIG_SOURCEDIRS
Definition config.h:436
#define CONFIG_INTERNALFILEPATTERNS
Definition config.h:403
#define CONFIG_ROOTDIR
Definition config.h:431
#define CONFIG_HEADERS
Definition config.h:390
#define CONFIG_OUTPUTFORMATS
Definition config.h:420
#define CONFIG_IMAGEEXTENSIONS
Definition config.h:452
#define CONFIG_EXCLUDEFILES
Definition config.h:385
#define CONFIG_EXCLUDEDIRS
Definition config.h:384
#define CONFIG_HEADERDIRS
Definition config.h:389
#define CONFIG_REPORTMISSINGALTTEXTFORIMAGES
Definition config.h:428
#define CONFIG_INCLUDEPATHS
Definition config.h:401
Combined button and popup list for selecting options.
#define SET(x, y)
static QString HEADERSTYLES
Definition config.h:298
static QString IGNORESINCE
Definition config.h:303
static QString CODESUFFIX
Definition config.h:278
static QString EXAMPLEDIRS
Definition config.h:287
static QString CODEINDENT
Definition config.h:275
static QString CODELANGUAGES
Definition config.h:276
static QString HEADERDIRS
Definition config.h:295
static QString ROOTDIR
Definition config.h:338
static QString HEADERS
Definition config.h:296
static QString NATURALLANGUAGE
Definition config.h:323
static QString WARNABOUTMISSINGPROJECTFILES
Definition config.h:363
static QString SINGLEEXEC
Definition config.h:342
static QString LANGUAGE
Definition config.h:315
static QString SOURCEDIRS
Definition config.h:343
static QString IGNOREDIRECTIVES
Definition config.h:301
static QString SHOWAUTOGENERATEDDOCS
Definition config.h:340
static QString EXCLUDEDIRS
Definition config.h:290
static QString INCLUSIVE
Definition config.h:311
static QString FILEEXTENSIONS
Definition config.h:358
static QString AUTOLINKERRORS
Definition config.h:273
static QString LOGWARNINGSDISABLECLIARGS
Definition config.h:319
static QString OUTPUTFORMATS
Definition config.h:327
static QString REDIRECTDOCUMENTATIONTODEVNULL
Definition config.h:334
static QString LANDINGPAGE
Definition config.h:313
static QString SCRIPTS
Definition config.h:339
static QString QHP
Definition config.h:336
static QString MODULEHEADER
Definition config.h:322
static QString DEPENDS
Definition config.h:282
static QString VERSION
Definition config.h:356
static QString CPPCLASSESPAGE
Definition config.h:279
static QString MACRO
Definition config.h:320
static QString PRODUCTNAME
Definition config.h:331
static QString SOURCES
Definition config.h:345
static QString BUILDVERSION
Definition config.h:274
static QString DOCBOOKEXTENSIONS
Definition config.h:284
static QString VERSIONSYM
Definition config.h:357
static QString IMAGEEXTENSIONS
Definition config.h:359
static QString ENDHEADER
Definition config.h:286
static QString EXAMPLESINSTALLPATH
Definition config.h:289
static QString DEFINES
Definition config.h:281
static QString INCLUDEPRIVATE
Definition config.h:309
static QString HEADERSCRIPTS
Definition config.h:297
static QString SOURCEENCODING
Definition config.h:344
static QString IGNORETOKENS
Definition config.h:302
static QString TAGFILE
Definition config.h:350
static QString IMAGEDIRS
Definition config.h:305
static QString NAVIGATION
Definition config.h:324
static QString SYNTAXHIGHLIGHTING
Definition config.h:348
static QString IGNOREWORDS
Definition config.h:304
static QString EXAMPLES
Definition config.h:288
static QString INCLUDEPATHS
Definition config.h:308
static QString WARNINGLIMIT
Definition config.h:364
static QString STYLESHEETS
Definition config.h:347
static QString LANDINGTITLE
Definition config.h:314
static QString USEALTTEXTASTITLE
Definition config.h:355
static QString CODEPREFIX
Definition config.h:277
static QString LOGPROGRESS
Definition config.h:317
static QString FALSEHOODS
Definition config.h:293
static QString HOMEPAGE
Definition config.h:299
static QString IMAGESOUTPUTDIR
Definition config.h:306
static QString TRADEMARKSPAGE
Definition config.h:353
static QString QMLTYPESPAGE
Definition config.h:360
static QString SPURIOUS
Definition config.h:346
static QString LOGWARNINGS
Definition config.h:318
static QString INDEXES
Definition config.h:312
static QString OUTPUTDIR
Definition config.h:326
static QString EXCLUDEFILES
Definition config.h:291
static QString EXTRAIMAGES
Definition config.h:292
static QString PRELIMINARY
Definition config.h:330
static QString TOCTITLES
Definition config.h:352
static QString NOLINKERRORS
Definition config.h:325
static QString LOCATIONINFO
Definition config.h:316
static QString TABSIZE
Definition config.h:349
static QString TIMESTAMPS
Definition config.h:351
static QString WARNABOUTMISSINGIMAGES
Definition config.h:362
static QString HOMETITLE
Definition config.h:300
static QString DOCUMENTATIONINHEADERS
Definition config.h:285
static QString SHOWINTERNAL
Definition config.h:341
static QString CPPCLASSESTITLE
Definition config.h:280
static QString QUOTINGINFORMATION
Definition config.h:337
static QString FORMATTING
Definition config.h:294
static QString PROJECT
Definition config.h:332
static QString MANIFESTMETA
Definition config.h:321
static QString OUTPUTPREFIXES
Definition config.h:328
static QString DESCRIPTION
Definition config.h:283
static QString REPORTMISSINGALTTEXTFORIMAGES
Definition config.h:335
static QString INTERNALFILEPATTERNS
Definition config.h:310
static QString OUTPUTSUFFIXES
Definition config.h:329
static QString URL
Definition config.h:354
static QString PROJECTROOT
Definition config.h:333
static QString QMLTYPESTITLE
Definition config.h:361