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