Warning, file /utilities/kate/addons/gdbplugin/json_placeholders.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include <QDir>
0007 #include <QFileInfo>
0008 #include <QJsonArray>
0009 #include <QJsonObject>
0010 #include <QProcess>
0011 #include <QRegularExpression>
0012 
0013 #include "json_placeholders.h"
0014 
0015 namespace json
0016 {
0017 static const QRegularExpression rx_placeholder(QLatin1String(R"--(\$\{(#?[a-z]+(?:\.[a-z]+)*)(?:\|([a-z]+))?\})--"), QRegularExpression::CaseInsensitiveOption);
0018 static const QRegularExpression rx_cast(QLatin1String(R"--(^\$\{(#?[a-z]+(?:\.[a-z]+)*)\|(int|bool|list)\}$)--"), QRegularExpression::CaseInsensitiveOption);
0019 
0020 std::optional<QString> valueAsString(const QJsonValue &);
0021 
0022 std::optional<QString> valueAsString(const QJsonArray &array, const bool quote = false)
0023 {
0024     if (array.isEmpty())
0025         return QString();
0026 
0027     if (array.size() == 1)
0028         return valueAsString(array.first());
0029 
0030     QStringList parts;
0031     for (const auto &item : array) {
0032         const auto text = valueAsString(item);
0033         if (!text)
0034             return std::nullopt;
0035         if (quote)
0036             parts << QStringLiteral("\"%s\"").arg(text.value());
0037         else
0038             parts << *text;
0039     }
0040     return parts.join(QStringLiteral(" "));
0041 }
0042 
0043 std::optional<QString> valueAsString(const QJsonValue &value)
0044 {
0045     if (value.isString())
0046         return value.toString();
0047     if (value.isArray())
0048         return valueAsString(value.toArray());
0049     if (value.isBool())
0050         return value.toBool() ? QStringLiteral("true") : QStringLiteral("false");
0051     if (value.isDouble())
0052         return QString::number(value.toDouble());
0053 
0054     return std::nullopt;
0055 }
0056 
0057 std::optional<QStringList> valueAsStringList(const QJsonValue &value)
0058 {
0059     if (value.isArray()) {
0060         QStringList listValue;
0061         for (const auto &item : value.toArray()) {
0062             const auto text = valueAsString(item);
0063             if (!text)
0064                 return std::nullopt;
0065             listValue << *text;
0066         }
0067         return listValue;
0068     } else {
0069         const auto text = valueAsString(value);
0070         if (text)
0071             return QProcess::splitCommand(*text);
0072     }
0073 
0074     return std::nullopt;
0075 }
0076 
0077 std::optional<bool> valueAsBool(const QJsonValue &value)
0078 {
0079     if (value.isBool())
0080         return value.toBool();
0081 
0082     const auto text = valueAsString(value);
0083     if (text) {
0084         const auto cleanText = text->trimmed();
0085         if (cleanText == QStringLiteral("true")) {
0086             return true;
0087         } else if (cleanText == QStringLiteral("false")) {
0088             return false;
0089         }
0090     }
0091     return std::nullopt;
0092 }
0093 
0094 std::optional<int> valueAsInt(const QJsonValue &value)
0095 {
0096     if (value.isDouble()) {
0097         return value.toInt();
0098     } else {
0099         const auto text = valueAsString(value);
0100         if (text) {
0101             bool ok = false;
0102             int intValue = text->trimmed().toInt(&ok, 10);
0103             if (ok) {
0104                 return intValue;
0105             }
0106         }
0107     }
0108     return std::nullopt;
0109 }
0110 
0111 /**
0112  * @brief apply_filter
0113  * Apply a variable filter
0114  * @param text
0115  * @param filter
0116  * @return
0117  */
0118 QString apply_filter(const QJsonValue &value, const QString &filter)
0119 {
0120     const QString text = valueAsString(value).value_or(QString());
0121     if (filter == QStringLiteral("base")) {
0122         return QFileInfo(text).baseName();
0123     }
0124     if (filter == QStringLiteral("dir")) {
0125         return QFileInfo(text).dir().dirName();
0126     }
0127     return text;
0128 }
0129 
0130 /**
0131  * @brief cast_from_string
0132  * @param text
0133  * @param variables
0134  * @return QJsonValue if the replacement is successful
0135  */
0136 std::optional<QJsonValue> cast_from_string(const QString &text, const VarMap &variables)
0137 {
0138     const auto match = rx_cast.match(text);
0139 
0140     if (!match.hasMatch()) {
0141         return std::nullopt;
0142     }
0143 
0144     const auto &key = match.captured(1);
0145     if (!variables.contains(key)) {
0146         return std::nullopt;
0147     }
0148 
0149     const auto &filter = match.captured(2);
0150     const auto value = variables[key];
0151 
0152     if (filter == QStringLiteral("int")) {
0153         const auto &intValue = valueAsInt(value);
0154         if (intValue)
0155             return intValue.value();
0156     } else if (filter == QStringLiteral("bool")) {
0157         const auto &boolValue = valueAsBool(value);
0158         if (boolValue)
0159             return boolValue.value();
0160     } else if (filter == QStringLiteral("list")) {
0161         const auto &listValue = valueAsStringList(value);
0162         if (listValue)
0163             return QJsonArray::fromStringList(*listValue);
0164     }
0165 
0166     return std::nullopt;
0167 }
0168 
0169 QJsonValue resolve(const QString &text, const VarMap &variables)
0170 {
0171     // try to cast to int/bool
0172     {
0173         auto casting = cast_from_string(text, variables);
0174         if (casting) {
0175             return *casting;
0176         }
0177     }
0178 
0179     QStringList parts;
0180 
0181     auto matches = rx_placeholder.globalMatch(text);
0182 
0183     int size = 0;
0184     while (matches.hasNext()) {
0185         const auto match = matches.next();
0186         const auto key = match.captured(1);
0187         if (!variables.contains(key)) {
0188             continue;
0189         }
0190         parts << text.mid(size, match.capturedStart(0)) << apply_filter(variables[key], match.captured(2));
0191         size = match.capturedEnd(0);
0192     }
0193     if (size == 0) {
0194         // not replaced
0195         return QJsonValue(text);
0196     }
0197     if (text.size() > size) {
0198         parts << text.mid(size);
0199     }
0200 
0201     return QJsonValue(parts.join(QStringLiteral("")));
0202 }
0203 
0204 QJsonObject resolve(const QJsonObject &map, const VarMap &variables)
0205 {
0206     QJsonObject replaced;
0207 
0208     for (auto it = map.begin(); it != map.end(); ++it) {
0209         const auto &key = it.key();
0210         replaced[key] = resolve(*it, variables);
0211     }
0212 
0213     return replaced;
0214 }
0215 
0216 QJsonArray resolve(const QJsonArray &array, const VarMap &variables)
0217 {
0218     QJsonArray replaced;
0219 
0220     for (const auto &value : array) {
0221         // if original == string and new is list, expand
0222         const auto newValue = resolve(value, variables);
0223         if (value.isString() && newValue.isArray()) {
0224             // concatenate
0225             for (const auto &item : newValue.toArray()) {
0226                 replaced << item;
0227             }
0228         } else {
0229             replaced << newValue;
0230         }
0231     }
0232 
0233     return replaced;
0234 }
0235 
0236 QJsonValue resolve(const QJsonValue &value, const VarMap &variables)
0237 {
0238     if (value.isObject())
0239         return resolve(value.toObject(), variables);
0240 
0241     if (value.isArray())
0242         return resolve(value.toArray(), variables);
0243 
0244     if (value.isString()) {
0245         return resolve(value.toString(), variables);
0246     }
0247 
0248     return value;
0249 }
0250 
0251 void findVariables(const QJsonObject &map, QSet<QString> &variables)
0252 {
0253     if (map.isEmpty())
0254         return;
0255     for (const auto &value : map) {
0256         findVariables(value, variables);
0257     }
0258 }
0259 
0260 void findVariables(const QJsonValue &value, QSet<QString> &variables)
0261 {
0262     if (value.isNull() || value.isUndefined())
0263         return;
0264     if (value.isObject()) {
0265         findVariables(value.toObject(), variables);
0266     } else if (value.isArray()) {
0267         findVariables(value.toArray(), variables);
0268     } else if (value.isString()) {
0269         findVariables(value.toString(), variables);
0270     }
0271 }
0272 
0273 void findVariables(const QJsonArray &array, QSet<QString> &variables)
0274 {
0275     if (array.isEmpty())
0276         return;
0277     for (const auto &value : array) {
0278         findVariables(value, variables);
0279     }
0280 }
0281 
0282 void findVariables(const QString &text, QSet<QString> &variables)
0283 {
0284     if (text.isNull() || text.isEmpty())
0285         return;
0286     auto matches = rx_placeholder.globalMatch(text);
0287     while (matches.hasNext()) {
0288         const auto match = matches.next();
0289         variables.insert(match.captured(1));
0290     }
0291 }
0292 
0293 }