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