File indexing completed on 2024-05-26 04:57:39
0001 /** 0002 * \file textcliformatter.cpp 0003 * CLI formatter for standard text input and output. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 28 Jul 2019 0008 * 0009 * Copyright (C) 2019-2024 Urs Fleisch 0010 * 0011 * This file is part of Kid3. 0012 * 0013 * Kid3 is free software; you can redistribute it and/or modify 0014 * it under the terms of the GNU General Public License as published by 0015 * the Free Software Foundation; either version 2 of the License, or 0016 * (at your option) any later version. 0017 * 0018 * Kid3 is distributed in the hope that it will be useful, 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0021 * GNU General Public License for more details. 0022 * 0023 * You should have received a copy of the GNU General Public License 0024 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0025 */ 0026 0027 #include "textcliformatter.h" 0028 #include <QDir> 0029 #include <QStringBuilder> 0030 #include "clierror.h" 0031 #include "abstractcli.h" 0032 #include "frame.h" 0033 0034 /** @cond */ 0035 namespace { 0036 0037 /** 0038 * Split string into command line arguments supporting quotes and escape 0039 * characters. 0040 * @param str command line string 0041 * @return list of arguments. 0042 */ 0043 QStringList splitArgs(const QString& str) 0044 { 0045 QStringList params; 0046 0047 for (int pos = 0; ; ) { 0048 QChar c; 0049 do { 0050 if (pos >= str.size()) 0051 return params; 0052 c = str.at(pos++); 0053 } while (c.isSpace()); 0054 QString param = QLatin1String(""); 0055 if (c == QLatin1Char('~')) { 0056 if (pos >= str.size() || str.at(pos).isSpace()) { 0057 params.append(QDir::homePath()); 0058 continue; 0059 } 0060 if (str.at(pos) == QLatin1Char('/')) { 0061 param = QDir::homePath(); 0062 c = QLatin1Char('/'); 0063 ++pos; 0064 } 0065 } 0066 do { 0067 if (c == QLatin1Char('"') || c == QLatin1Char('\'')) { 0068 const QChar quote = c; 0069 for (;;) { 0070 if (pos >= str.size()) 0071 return QStringList(); 0072 c = str.at(pos++); 0073 if (c == quote) 0074 break; 0075 if (c == QLatin1Char('\\')) { 0076 if (pos >= str.size()) 0077 return QStringList(); 0078 c = str.at(pos++); 0079 if (c != quote && c != QLatin1Char('\\')) 0080 param += QLatin1Char('\\'); 0081 } 0082 param += c; 0083 } 0084 } else { 0085 if (c == QLatin1Char('\\')) { 0086 if (pos >= str.size()) 0087 return QStringList(); 0088 c = str.at(pos++); 0089 } 0090 param += c; 0091 } 0092 if (pos >= str.size()) 0093 break; 0094 c = str.at(pos++); 0095 } while (!c.isSpace()); 0096 params.append(param); 0097 } 0098 } 0099 0100 /** 0101 * Print list of files. 0102 * @param io CLI I/O 0103 * @param lst file properties 0104 * @param indent number of spaces to indent 0105 */ 0106 void printFiles(AbstractCliIO* io, const QVariantList& lst, int indent) 0107 { 0108 if (lst.isEmpty()) 0109 return; 0110 0111 for (const QVariant& var : lst) { 0112 const QVariantMap map = var.toMap(); 0113 QString propsStr = map.value(QLatin1String("selected")).toBool() 0114 ? QLatin1String(">") : QLatin1String(" "); 0115 propsStr += 0116 map.value(QLatin1String("changed")).toBool() ? QLatin1String("*") : QLatin1String(" "); 0117 if (map.contains(QLatin1String("tags"))) { 0118 const QVariantList tags = map.value(QLatin1String("tags")).toList(); 0119 FOR_ALL_TAGS(tagNr) { 0120 propsStr += tags.contains(1 + tagNr) 0121 ? QLatin1Char('1' + tagNr) : QLatin1Char('-'); 0122 } 0123 } else { 0124 propsStr += QString(Frame::Tag_NumValues, QLatin1Char(' ')); 0125 } 0126 io->writeLine(propsStr + QString(indent, QLatin1Char(' ')) + 0127 map.value(QLatin1String("fileName")).toString()); 0128 if (map.contains(QLatin1String("files"))) { 0129 printFiles(io, map.value(QLatin1String("files")).toList(), indent + 2); 0130 } 0131 } 0132 } 0133 0134 } 0135 /** @endcond */ 0136 0137 0138 TextCliFormatter::TextCliFormatter(AbstractCliIO* io) 0139 : AbstractCliFormatter(io) 0140 { 0141 } 0142 0143 TextCliFormatter::~TextCliFormatter() 0144 { 0145 } 0146 0147 void TextCliFormatter::clear() 0148 { 0149 m_errorMessage.clear(); 0150 m_args.clear(); 0151 } 0152 0153 QStringList TextCliFormatter::parseArguments(const QString& line) 0154 { 0155 m_errorMessage.clear(); 0156 m_args = splitArgs(line); 0157 return m_args; 0158 } 0159 0160 QString TextCliFormatter::getErrorMessage() const 0161 { 0162 return m_errorMessage; 0163 } 0164 0165 bool TextCliFormatter::isIncomplete() const 0166 { 0167 return false; 0168 } 0169 0170 bool TextCliFormatter::isFormatRecognized() const 0171 { 0172 return !m_args.isEmpty(); 0173 } 0174 0175 void TextCliFormatter::writeError(CliError errorCode) 0176 { 0177 QString errorMsg; 0178 switch (errorCode) { 0179 case CliError::MethodNotFound: 0180 #if QT_VERSION >= 0x050600 0181 errorMsg = tr("Unknown command '%1'. Type 'help' for help.") 0182 .arg(m_args.isEmpty() ? QLatin1String("") : m_args.constFirst()); 0183 #else 0184 errorMsg = tr("Unknown command '%1'. Type 'help' for help.") 0185 .arg(m_args.isEmpty() ? QLatin1String("") : m_args.first()); 0186 #endif 0187 break; 0188 default: 0189 ; 0190 } 0191 if (!errorMsg.isEmpty()) { 0192 writeError(errorMsg); 0193 } 0194 } 0195 0196 void TextCliFormatter::writeError(const QString& msg) 0197 { 0198 io()->writeErrorLine(msg); 0199 } 0200 0201 void TextCliFormatter::writeError(const QString& msg, CliError errorCode) 0202 { 0203 if (errorCode == CliError::Usage) { 0204 io()->writeLine(tr("Usage:")); 0205 } 0206 writeError(msg); 0207 } 0208 0209 void TextCliFormatter::writeResult(const QString& str) 0210 { 0211 io()->writeLine(str); 0212 } 0213 0214 /** 0215 * Write result message. 0216 * @param strs result as string list 0217 */ 0218 void TextCliFormatter::writeResult(const QStringList& strs) 0219 { 0220 for (const QString& str : strs) { 0221 io()->writeLine(str); 0222 } 0223 } 0224 0225 void TextCliFormatter::writeResult(bool result) 0226 { 0227 io()->writeLine(QLatin1String(result ? "true" : "false")); 0228 } 0229 0230 void TextCliFormatter::writeResult(const QVariantMap& map) 0231 { 0232 for (auto it = map.constBegin(); it != map.constEnd(); ++it) { 0233 if (const QString& key = it.key(); key == QLatin1String("tags")) { 0234 QVariantList value = it.value().toList(); 0235 QString tagStr; 0236 for (const QVariant& var : value) { 0237 if (!tagStr.isEmpty()) { 0238 tagStr += QLatin1String(", "); 0239 } 0240 tagStr += var.toString(); 0241 } 0242 if (tagStr.isEmpty()) { 0243 tagStr = QLatin1String("-"); 0244 } 0245 io()->writeLine(tr("Tags") + QLatin1String(": ") + tagStr); 0246 } else if (key == QLatin1String("taggedFile")) { 0247 QVariantMap value = it.value().toMap(); 0248 QString m_detailInfo = value.value(QLatin1String("format")).toString(); 0249 QString m_filename = value.value(QLatin1String("fileName")).toString(); 0250 bool m_fileNameChanged = value.value(QLatin1String("fileNameChanged")).toBool(); 0251 if (!m_detailInfo.isEmpty()) { 0252 io()->writeLine(tr("File") + QLatin1String(": ") + m_detailInfo); 0253 } 0254 if (!m_filename.isEmpty()) { 0255 QString line = m_fileNameChanged ? QLatin1String("*") : QLatin1String(" "); 0256 line += QLatin1Char(' '); 0257 line += tr("Name"); 0258 line += QLatin1String(": "); 0259 line += m_filename; 0260 io()->writeLine(line); 0261 } 0262 FOR_ALL_TAGS(tagNr) { 0263 QString tagNrStr = Frame::tagNumberToString(tagNr); 0264 if (const QVariantMap tag = value.value(QLatin1String("tag") + tagNrStr).toMap(); 0265 !tag.isEmpty()) { 0266 if (const QVariantList frames = tag.value(QLatin1String("frames")).toList(); 0267 !frames.isEmpty()) { 0268 int maxLength = 0; 0269 for (const QVariant& var : frames) { 0270 QString name = var.toMap().value(QLatin1String("name")).toString(); 0271 maxLength = qMax(name.size(), maxLength); 0272 } 0273 QString tagStr = tag.value(QLatin1String("format")).toString(); 0274 if (!tagStr.isEmpty()) { 0275 tagStr.prepend(QLatin1Char(' ')); 0276 } 0277 tagStr.prepend(QLatin1Char(':')); 0278 tagStr.prepend(tr("Tag %1").arg(tagNrStr)); 0279 io()->writeLine(tagStr); 0280 for (const QVariant& var : frames) { 0281 QString name = var.toMap().value(QLatin1String("name")).toString(); 0282 QString frameValue = var.toMap().value(QLatin1String("value")).toString(); 0283 bool changed = var.toMap().value(QLatin1String("changed")).toBool(); 0284 QString line = changed ? QLatin1String("*") : QLatin1String(" "); 0285 line += QLatin1Char(' '); 0286 line += name; 0287 line += QString(maxLength - name.size() + 2, 0288 QLatin1Char(' ')); 0289 line += frameValue; 0290 io()->writeLine(line); 0291 } 0292 } 0293 } 0294 } 0295 } else if (key == QLatin1String("files")) { 0296 printFiles(io(), it.value().toList(), 1); 0297 } else if (key == QLatin1String("timeout")) { 0298 QString value = it.value().toString(); 0299 io()->writeLine(tr("Timeout") % QLatin1String(": ") % value); 0300 } else if (key == QLatin1String("event")) { 0301 QVariantMap value = it.value().toMap(); 0302 QString type = value.value(QLatin1String("type")).toString(); 0303 QString eventText; 0304 if (type == QLatin1String("readingDirectory")) { 0305 eventText = tr("Reading Folder"); 0306 } else if (type == QLatin1String("started")) { 0307 eventText = tr("Started"); 0308 } else if (type == QLatin1String("source")) { 0309 eventText = tr("Source"); 0310 } else if (type == QLatin1String("querying")) { 0311 eventText = tr("Querying"); 0312 } else if (type == QLatin1String("fetching")) { 0313 eventText = tr("Fetching"); 0314 } else if (type == QLatin1String("dataReceived")) { 0315 eventText = tr("Data received"); 0316 } else if (type == QLatin1String("cover")) { 0317 eventText = tr("Cover"); 0318 } else if (type == QLatin1String("finished")) { 0319 eventText = tr("Finished"); 0320 } else if (type == QLatin1String("aborted")) { 0321 eventText = tr("Aborted"); 0322 } else if (type == QLatin1String("error")) { 0323 eventText = tr("Error"); 0324 } else if (type == QLatin1String("parseError")) { 0325 eventText = QLatin1String("parse error"); 0326 } else { 0327 eventText = type; 0328 } 0329 QVariant data = value.value(QLatin1String("data")); 0330 #if QT_VERSION >= 0x060000 0331 if (data.typeId() == QMetaType::QString) { 0332 #else 0333 if (data.type() == QVariant::String) { 0334 #endif 0335 if (QString text = data.toString(); !text.isEmpty()) { 0336 if (type == QLatin1String("filterEntered")) { 0337 eventText = QLatin1String(" ") + text; 0338 } else if (type == QLatin1String("filterPassed")) { 0339 eventText = QLatin1String("+ ") + text; 0340 } else if (type == QLatin1String("filteredOut")) { 0341 eventText = QLatin1String("- ") + text; 0342 } else { 0343 eventText += QLatin1String(": "); 0344 eventText += text; 0345 } 0346 } 0347 #if QT_VERSION >= 0x060000 0348 } else if (data.typeId() == QMetaType::QVariantMap) { 0349 #else 0350 } else if (data.type() == QVariant::Map) { 0351 #endif 0352 // Event maps with source and destination are used with 0353 // rename directory events. 0354 QVariantMap dataMap = data.toMap(); 0355 if (dataMap.contains(QLatin1String("source"))) { 0356 eventText += QLatin1String(" "); 0357 eventText += dataMap.value(QLatin1String("source")).toString(); 0358 } 0359 if (dataMap.contains(QLatin1String("destination"))) { 0360 eventText += QLatin1String("\n "); 0361 eventText += dataMap.value(QLatin1String("destination")).toString(); 0362 } 0363 } 0364 io()->writeLine(eventText); 0365 } 0366 } 0367 } 0368 0369 void TextCliFormatter::finishWriting() 0370 { 0371 }