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