1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "macroexpander.h"
27
28 #include "algorithm.h"
29 #include "fileutils.h"
30 #include "commandline.h"
31 #include "qtcassert.h"
32 #include "stringutils.h"
33
34 #include <QCoreApplication>
35 #include <QDebug>
36 #include <QDir>
37 #include <QFileInfo>
38 #include <QLoggingCategory>
39 #include <QMap>
40
41 namespace Utils {
42 namespace Internal {
43
44 static Q_LOGGING_CATEGORY(expanderLog, "qtc.utils.macroexpander", QtWarningMsg)
45
46 const char kFilePathPostfix[] = ":FilePath";
47 const char kPathPostfix[] = ":Path";
48 const char kNativeFilePathPostfix[] = ":NativeFilePath";
49 const char kNativePathPostfix[] = ":NativePath";
50 const char kFileNamePostfix[] = ":FileName";
51 const char kFileBaseNamePostfix[] = ":FileBaseName";
52
53 class MacroExpanderPrivate : public AbstractMacroExpander
54 {
55 public:
56 MacroExpanderPrivate() = default;
57
resolveMacro(const QString & name,QString * ret,QSet<AbstractMacroExpander * > & seen)58 bool resolveMacro(const QString &name, QString *ret, QSet<AbstractMacroExpander *> &seen) override
59 {
60 // Prevent loops:
61 const int count = seen.count();
62 seen.insert(this);
63 if (seen.count() == count)
64 return false;
65
66 bool found;
67 *ret = value(name.toUtf8(), &found);
68 if (found)
69 return true;
70
71 found = Utils::anyOf(m_subProviders, [name, ret, &seen] (const MacroExpanderProvider &p) -> bool {
72 MacroExpander *expander = p ? p() : 0;
73 return expander && expander->d->resolveMacro(name, ret, seen);
74 });
75
76 if (found)
77 return true;
78
79 found = Utils::anyOf(m_extraResolvers, [name, ret] (const MacroExpander::ResolverFunction &resolver) {
80 return resolver(name, ret);
81 });
82
83 if (found)
84 return true;
85
86 return this == globalMacroExpander()->d ? false : globalMacroExpander()->d->resolveMacro(name, ret, seen);
87 }
88
value(const QByteArray & variable,bool * found) const89 QString value(const QByteArray &variable, bool *found) const
90 {
91 MacroExpander::StringFunction sf = m_map.value(variable);
92 if (sf) {
93 if (found)
94 *found = true;
95 return sf();
96 }
97
98 for (auto it = m_prefixMap.constBegin(); it != m_prefixMap.constEnd(); ++it) {
99 if (variable.startsWith(it.key())) {
100 MacroExpander::PrefixFunction pf = it.value();
101 if (found)
102 *found = true;
103 return pf(QString::fromUtf8(variable.mid(it.key().count())));
104 }
105 }
106 if (found)
107 *found = false;
108
109 return QString();
110 }
111
112 QHash<QByteArray, MacroExpander::StringFunction> m_map;
113 QHash<QByteArray, MacroExpander::PrefixFunction> m_prefixMap;
114 QVector<MacroExpander::ResolverFunction> m_extraResolvers;
115 QMap<QByteArray, QString> m_descriptions;
116 QString m_displayName;
117 QVector<MacroExpanderProvider> m_subProviders;
118 bool m_accumulating = false;
119
120 bool m_aborted = false;
121 int m_lockDepth = 0;
122 };
123
124 } // Internal
125
126 using namespace Internal;
127
128 /*!
129 \class Utils::MacroExpander
130 \brief The MacroExpander class manages \QC wide variables, that a user
131 can enter into many string settings. The variables are replaced by an actual value when the string
132 is used, similar to how environment variables are expanded by a shell.
133
134 \section1 Variables
135
136 Variable names can be basically any string without dollar sign and braces,
137 though it is recommended to only use 7-bit ASCII without special characters and whitespace.
138
139 If there are several variables that contain different aspects of the same object,
140 it is convention to give them the same prefix, followed by a colon and a postfix
141 that describes the aspect.
142 Examples of this are \c{CurrentDocument:FilePath} and \c{CurrentDocument:Selection}.
143
144 When the variable manager is requested to replace variables in a string, it looks for
145 variable names enclosed in %{ and }, like %{CurrentDocument:FilePath}.
146
147 Environment variables are accessible using the %{Env:...} notation.
148 For example, to access the SHELL environment variable, use %{Env:SHELL}.
149
150 \note The names of the variables are stored as QByteArray. They are typically
151 7-bit-clean. In cases where this is not possible, UTF-8 encoding is
152 assumed.
153
154 \section1 Providing Variable Values
155
156 Plugins can register variables together with a description through registerVariable().
157 A typical setup is to register variables in the Plugin::initialize() function.
158
159 \code
160 bool MyPlugin::initialize(const QStringList &arguments, QString *errorString)
161 {
162 [...]
163 MacroExpander::registerVariable(
164 "MyVariable",
165 tr("The current value of whatever I want."));
166 []() -> QString {
167 QString value;
168 // do whatever is necessary to retrieve the value
169 [...]
170 return value;
171 }
172 );
173 [...]
174 }
175 \endcode
176
177
178 For variables that refer to a file, you should use the convenience function
179 MacroExpander::registerFileVariables().
180 The functions take a variable prefix, like \c MyFileVariable,
181 and automatically handle standardized postfixes like \c{:FilePath},
182 \c{:Path} and \c{:FileBaseName}, resulting in the combined variables, such as
183 \c{MyFileVariable:FilePath}.
184
185 \section1 Providing and Expanding Parametrized Strings
186
187 Though it is possible to just ask the variable manager for the value of some variable in your
188 code, the preferred use case is to give the user the possibility to parametrize strings, for
189 example for settings.
190
191 (If you ever think about doing the former, think twice. It is much more efficient
192 to just ask the plugin that provides the variable value directly, without going through
193 string conversions, and through the variable manager which will do a large scale poll. To be
194 more concrete, using the example from the Providing Variable Values section: instead of
195 calling \c{MacroExpander::value("MyVariable")}, it is much more efficient to just ask directly
196 with \c{MyPlugin::variableValue()}.)
197
198 \section2 User Interface
199
200 If the string that you want to parametrize is settable by the user, through a QLineEdit or
201 QTextEdit derived class, you should add a variable chooser to your UI, which allows adding
202 variables to the string by browsing through a list. See Utils::VariableChooser for more
203 details.
204
205 \section2 Expanding Strings
206
207 Expanding variable values in strings is done by "macro expanders".
208 Utils::AbstractMacroExpander is the base class for these, and the variable manager
209 provides an implementation that expands \QC variables through
210 MacroExpander::macroExpander().
211
212 There are several different ways to expand a string, covering the different use cases,
213 listed here sorted by relevance:
214 \list
215 \li Using MacroExpander::expandedString(). This is the most comfortable way to get a string
216 with variable values expanded, but also the least flexible one. If this is sufficient for
217 you, use it.
218 \li Using the Utils::expandMacros() functions. These take a string and a macro expander (for which
219 you would use the one provided by the variable manager). Mostly the same as
220 MacroExpander::expandedString(), but also has a variant that does the replacement inline
221 instead of returning a new string.
222 \li Using Utils::QtcProcess::expandMacros(). This expands the string while conforming to the
223 quoting rules of the platform it is run on. Use this function with the variable manager's
224 macro expander if your string will be passed as a command line parameter string to an
225 external command.
226 \li Writing your own macro expander that nests the variable manager's macro expander. And then
227 doing one of the above. This allows you to expand additional "local" variables/macros,
228 that do not come from the variable manager.
229 \endlist
230
231 */
232
233 /*!
234 * \internal
235 */
MacroExpander()236 MacroExpander::MacroExpander()
237 {
238 d = new MacroExpanderPrivate;
239 }
240
241 /*!
242 * \internal
243 */
~MacroExpander()244 MacroExpander::~MacroExpander()
245 {
246 delete d;
247 }
248
249 /*!
250 * \internal
251 */
resolveMacro(const QString & name,QString * ret) const252 bool MacroExpander::resolveMacro(const QString &name, QString *ret) const
253 {
254 QSet<AbstractMacroExpander*> seen;
255 return d->resolveMacro(name, ret, seen);
256 }
257
258 /*!
259 * Returns the value of the given \a variable. If \a found is given, it is
260 * set to true if the variable has a value at all, false if not.
261 */
value(const QByteArray & variable,bool * found) const262 QString MacroExpander::value(const QByteArray &variable, bool *found) const
263 {
264 return d->value(variable, found);
265 }
266
267 /*!
268 * Returns \a stringWithVariables with all variables replaced by their values.
269 * See the MacroExpander overview documentation for other ways to expand variables.
270 *
271 * \sa MacroExpander
272 * \sa macroExpander()
273 */
expand(const QString & stringWithVariables) const274 QString MacroExpander::expand(const QString &stringWithVariables) const
275 {
276 if (d->m_lockDepth == 0)
277 d->m_aborted = false;
278
279 if (d->m_lockDepth > 10) { // Limit recursion.
280 d->m_aborted = true;
281 return QString();
282 }
283
284 ++d->m_lockDepth;
285
286 QString res = stringWithVariables;
287 Utils::expandMacros(&res, d);
288
289 --d->m_lockDepth;
290
291 if (d->m_lockDepth == 0 && d->m_aborted)
292 return tr("Infinite recursion error") + QLatin1String(": ") + stringWithVariables;
293
294 return res;
295 }
296
expand(const FilePath & fileNameWithVariables) const297 FilePath MacroExpander::expand(const FilePath &fileNameWithVariables) const
298 {
299 FilePath result = fileNameWithVariables;
300 result.setPath(expand(result.path()));
301 result.setHost(expand(result.host()));
302 result.setScheme(expand(result.scheme()));
303 return result;
304 }
305
expand(const QByteArray & stringWithVariables) const306 QByteArray MacroExpander::expand(const QByteArray &stringWithVariables) const
307 {
308 return expand(QString::fromLatin1(stringWithVariables)).toLatin1();
309 }
310
expandVariant(const QVariant & v) const311 QVariant MacroExpander::expandVariant(const QVariant &v) const
312 {
313 const auto type = QMetaType::Type(v.type());
314 if (type == QMetaType::QString) {
315 return expand(v.toString());
316 } else if (type == QMetaType::QStringList) {
317 return Utils::transform(v.toStringList(),
318 [this](const QString &s) -> QVariant { return expand(s); });
319 } else if (type == QMetaType::QVariantList) {
320 return Utils::transform(v.toList(), [this](const QVariant &v) { return expandVariant(v); });
321 } else if (type == QMetaType::QVariantMap) {
322 const auto map = v.toMap();
323 QVariantMap result;
324 for (auto it = map.cbegin(), end = map.cend(); it != end; ++it)
325 result.insert(it.key(), expandVariant(it.value()));
326 return result;
327 }
328 return v;
329 }
330
expandProcessArgs(const QString & argsWithVariables) const331 QString MacroExpander::expandProcessArgs(const QString &argsWithVariables) const
332 {
333 QString result = argsWithVariables;
334 const bool ok = ProcessArgs::expandMacros(&result, d);
335 QTC_ASSERT(ok, qCDebug(expanderLog) << "Expanding failed: " << argsWithVariables);
336 return result;
337 }
338
fullPrefix(const QByteArray & prefix)339 static QByteArray fullPrefix(const QByteArray &prefix)
340 {
341 QByteArray result = prefix;
342 if (!result.endsWith(':'))
343 result.append(':');
344 return result;
345 }
346
347 /*!
348 * Makes the given string-valued \a prefix known to the variable manager,
349 * together with a localized \a description.
350 *
351 * The \a value PrefixFunction will be called and gets the full variable name
352 * with the prefix stripped as input.
353 *
354 * \sa registerVariables(), registerIntVariable(), registerFileVariables()
355 */
registerPrefix(const QByteArray & prefix,const QString & description,const MacroExpander::PrefixFunction & value,bool visible)356 void MacroExpander::registerPrefix(const QByteArray &prefix, const QString &description,
357 const MacroExpander::PrefixFunction &value, bool visible)
358 {
359 QByteArray tmp = fullPrefix(prefix);
360 if (visible)
361 d->m_descriptions.insert(tmp + "<value>", description);
362 d->m_prefixMap.insert(tmp, value);
363 }
364
365 /*!
366 * Makes the given string-valued \a variable known to the variable manager,
367 * together with a localized \a description.
368 *
369 * \sa registerFileVariables(), registerIntVariable(), registerPrefix()
370 */
registerVariable(const QByteArray & variable,const QString & description,const StringFunction & value,bool visibleInChooser)371 void MacroExpander::registerVariable(const QByteArray &variable,
372 const QString &description, const StringFunction &value, bool visibleInChooser)
373 {
374 if (visibleInChooser)
375 d->m_descriptions.insert(variable, description);
376 d->m_map.insert(variable, value);
377 }
378
379 /*!
380 * Makes the given integral-valued \a variable known to the variable manager,
381 * together with a localized \a description.
382 *
383 * \sa registerVariable(), registerFileVariables(), registerPrefix()
384 */
registerIntVariable(const QByteArray & variable,const QString & description,const MacroExpander::IntFunction & value)385 void MacroExpander::registerIntVariable(const QByteArray &variable,
386 const QString &description, const MacroExpander::IntFunction &value)
387 {
388 const MacroExpander::IntFunction valuecopy = value; // do not capture a reference in a lambda
389 registerVariable(variable, description,
390 [valuecopy]() { return QString::number(valuecopy ? valuecopy() : 0); });
391 }
392
393 /*!
394 * Convenience function to register several variables with the same \a prefix, that have a file
395 * as a value. Takes the prefix and registers variables like \c{prefix:FilePath} and
396 * \c{prefix:Path}, with descriptions that start with the given \a heading.
397 * For example \c{registerFileVariables("CurrentDocument", tr("Current Document"))} registers
398 * variables such as \c{CurrentDocument:FilePath} with description
399 * "Current Document: Full path including file name."
400 *
401 * \sa registerVariable(), registerIntVariable(), registerPrefix()
402 */
registerFileVariables(const QByteArray & prefix,const QString & heading,const FileFunction & base,bool visibleInChooser)403 void MacroExpander::registerFileVariables(const QByteArray &prefix,
404 const QString &heading, const FileFunction &base, bool visibleInChooser)
405 {
406 registerVariable(prefix + kFilePathPostfix,
407 tr("%1: Full path including file name.").arg(heading),
408 [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : QFileInfo(tmp).filePath(); },
409 visibleInChooser);
410
411 registerVariable(prefix + kPathPostfix,
412 tr("%1: Full path excluding file name.").arg(heading),
413 [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : QFileInfo(tmp).path(); },
414 visibleInChooser);
415
416 registerVariable(prefix + kNativeFilePathPostfix,
417 tr("%1: Full path including file name, with native path separator (backslash on Windows).").arg(heading),
418 [base]() -> QString {
419 QString tmp = base().toString();
420 return tmp.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(tmp).filePath());
421 },
422 visibleInChooser);
423
424 registerVariable(prefix + kNativePathPostfix,
425 tr("%1: Full path excluding file name, with native path separator (backslash on Windows).").arg(heading),
426 [base]() -> QString {
427 QString tmp = base().toString();
428 return tmp.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(tmp).path());
429 },
430 visibleInChooser);
431
432 registerVariable(prefix + kFileNamePostfix,
433 tr("%1: File name without path.").arg(heading),
434 [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : FilePath::fromString(tmp).fileName(); },
435 visibleInChooser);
436
437 registerVariable(prefix + kFileBaseNamePostfix,
438 tr("%1: File base name without path and suffix.").arg(heading),
439 [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : QFileInfo(tmp).baseName(); },
440 visibleInChooser);
441 }
442
registerExtraResolver(const MacroExpander::ResolverFunction & value)443 void MacroExpander::registerExtraResolver(const MacroExpander::ResolverFunction &value)
444 {
445 d->m_extraResolvers.append(value);
446 }
447
448 /*!
449 * Returns all registered variable names.
450 *
451 * \sa registerVariable()
452 * \sa registerFileVariables()
453 */
visibleVariables() const454 QList<QByteArray> MacroExpander::visibleVariables() const
455 {
456 return d->m_descriptions.keys();
457 }
458
459 /*!
460 * Returns the description that was registered for the \a variable.
461 */
variableDescription(const QByteArray & variable) const462 QString MacroExpander::variableDescription(const QByteArray &variable) const
463 {
464 return d->m_descriptions.value(variable);
465 }
466
isPrefixVariable(const QByteArray & variable) const467 bool MacroExpander::isPrefixVariable(const QByteArray &variable) const
468 {
469 return d->m_prefixMap.contains(fullPrefix(variable));
470 }
471
subProviders() const472 MacroExpanderProviders MacroExpander::subProviders() const
473 {
474 return d->m_subProviders;
475 }
476
displayName() const477 QString MacroExpander::displayName() const
478 {
479 return d->m_displayName;
480 }
481
setDisplayName(const QString & displayName)482 void MacroExpander::setDisplayName(const QString &displayName)
483 {
484 d->m_displayName = displayName;
485 }
486
registerSubProvider(const MacroExpanderProvider & provider)487 void MacroExpander::registerSubProvider(const MacroExpanderProvider &provider)
488 {
489 d->m_subProviders.append(provider);
490 }
491
isAccumulating() const492 bool MacroExpander::isAccumulating() const
493 {
494 return d->m_accumulating;
495 }
496
setAccumulating(bool on)497 void MacroExpander::setAccumulating(bool on)
498 {
499 d->m_accumulating = on;
500 }
501
502 class GlobalMacroExpander : public MacroExpander
503 {
504 public:
GlobalMacroExpander()505 GlobalMacroExpander()
506 {
507 setDisplayName(MacroExpander::tr("Global variables"));
508 registerPrefix("Env", MacroExpander::tr("Access environment variables."),
509 [](const QString &value) { return QString::fromLocal8Bit(qgetenv(value.toLocal8Bit())); });
510 }
511 };
512
513 /*!
514 * Returns the expander for globally registered variables.
515 */
globalMacroExpander()516 MacroExpander *globalMacroExpander()
517 {
518 static GlobalMacroExpander theGlobalExpander;
519 return &theGlobalExpander;
520 }
521
522 } // namespace Utils
523