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