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