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 }