File indexing completed on 2024-04-21 13:19:24

0001 // clang-format off
0002 /*
0003  * KDiff3 - Text Diff And Merge Tool
0004  *
0005  * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de
0006  * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com
0007  * SPDX-License-Identifier: GPL-2.0-or-later
0008  */
0009 // clang-format on
0010 #include "Utils.h"
0011 
0012 #include "compat.h"
0013 #include "fileaccess.h"
0014 #include "TypeUtils.h"
0015 
0016 #include <QString>
0017 #include <QStringList>
0018 #include <QHash>
0019 #include <QRegularExpression>
0020 
0021 /* Split the command line into arguments.
0022  * Normally split at white space separators except when quoting with " or '.
0023  * Backslash is treated as meta.
0024  * Detect parsing errors like unclosed quotes.
0025  * The first item in the list will be the command itself.
0026  * Returns the error reason as string or an empty string on success.
0027  * Eg. >"1" "2"<           => >1<, >2<
0028  * Eg. >'\'\\'<            => >'\<   backslash is a meta character
0029  * Eg. > "\\" <            => >\<
0030  * Eg. >"c:\sed" 's/a/\' /g'<  => >c:\sed<, >s/a/' /g<
0031  */
0032 QString Utils::getArguments(QString cmd, QString& program, QStringList& args)
0033 {
0034     program = QString();
0035     args.clear();
0036     for(QtSizeType i = 0; i < cmd.length(); ++i)
0037     {
0038         while(i < cmd.length() && cmd[i].isSpace())
0039         {
0040             ++i;
0041         }
0042         if(cmd[i] == '"' || cmd[i] == '\'') // argument beginning with a quote
0043         {
0044             QChar quoteChar = cmd[i];
0045             ++i;
0046             QtSizeType argStart = i;
0047             bool bSkip = false;
0048             while(i < cmd.length() && (cmd[i] != quoteChar || bSkip))
0049             {
0050                 if(bSkip)
0051                 {
0052                     bSkip = false;
0053                     //Don't emulate bash here we are not talking to it.
0054                     //For us all quotes are the same.
0055                     if(cmd[i] == '\\' || cmd[i] == '\'' || cmd[i] == '"')
0056                     {
0057                         cmd.remove(i - 1, 1); // remove the backslash '\'
0058                         continue;
0059                     }
0060                 }
0061                 else if(cmd[i] == '\\')
0062                     bSkip = true;
0063                 ++i;
0064             }
0065             if(i < cmd.length())
0066             {
0067                 args << cmd.mid(argStart, i - argStart);
0068                 if(i + 1 < cmd.length() && !cmd[i + 1].isSpace())
0069                     return i18n("Expecting space after closing quote.");
0070             }
0071             else
0072                 return i18n("Unmatched quote.");
0073             continue;
0074         }
0075         else
0076         {
0077             QtSizeType argStart = i;
0078             while(i < cmd.length() && (!cmd[i].isSpace() /*|| bSkip*/))
0079             {
0080                 if(cmd[i] == '"' || cmd[i] == '\'')
0081                     return i18n("Unexpected quote character within argument.");
0082                 ++i;
0083             }
0084             args << cmd.mid(argStart, i - argStart);
0085         }
0086     }
0087     if(args.isEmpty())
0088         return i18n("No program specified.");
0089     else
0090     {
0091         program = args[0];
0092         args.pop_front();
0093     }
0094     return QString();
0095 }
0096 
0097 bool Utils::wildcardMultiMatch(const QString& wildcard, const QString& testString, bool bCaseSensitive)
0098 {
0099     static QHash<QString, QRegularExpression> s_patternMap;
0100 
0101     const QStringList regExpList = wildcard.split(QChar(';'));
0102 
0103     for(const QString& regExp : regExpList)
0104     {
0105         QHash<QString, QRegularExpression>::iterator patIt = s_patternMap.find(regExp);
0106         if(patIt == s_patternMap.end())
0107         {
0108             QRegularExpression pattern(QRegularExpression::wildcardToRegularExpression(regExp), bCaseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption);
0109             patIt = s_patternMap.insert(regExp, pattern);
0110         }
0111 
0112         if(patIt.value().match(testString).hasMatch())
0113             return true;
0114     }
0115 
0116     return false;
0117 }
0118 
0119 //TODO: Only used by calcTokenPos.
0120 bool Utils::isCTokenChar(QChar c)
0121 {
0122     return (c == '_') ||
0123            (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
0124            (c >= '0' && c <= '9');
0125 }
0126 
0127 
0128 //TODO: Needed? Only user of isCTokenChar.
0129 /// Calculate where a token starts and ends, given the x-position on screen.
0130 void Utils::calcTokenPos(const QString& s, qint32 posOnScreen, QtSizeType& pos1, QtSizeType& pos2)
0131 {
0132     QtSizeType pos = std::max(0, posOnScreen);
0133     if(pos >= s.length())
0134     {
0135         pos1 = s.length();
0136         pos2 = s.length();
0137         return;
0138     }
0139 
0140     pos1 = pos;
0141     pos2 = pos + 1;
0142 
0143     if(isCTokenChar(s[pos1]))
0144     {
0145         while(pos1 >= 0 && isCTokenChar(s[pos1]))
0146             --pos1;
0147         ++pos1;
0148 
0149         while(pos2 < s.length() && isCTokenChar(s[pos2]))
0150             ++pos2;
0151     }
0152 }
0153 
0154 QString Utils::calcHistoryLead(const QString& s)
0155 {
0156     static const QRegularExpression nonWhitespace("\\S"), whitespace("\\s");
0157 
0158     // Return the start of the line until the first white char after the first non white char.
0159     QtSizeType i = s.indexOf(nonWhitespace);
0160     if(i == -1)
0161         return QString("");
0162 
0163     i = s.indexOf(whitespace, i);
0164     if(Q_UNLIKELY(i == -1))
0165         return s;// Very unlikely
0166 
0167     return s.left(i);
0168 }
0169 
0170 /*
0171     QUrl::toLocalFile does some special handling for locally visable windows network drives.
0172     If QUrl::isLocal however it returns false and we get an empty string back.
0173 */
0174 QString Utils::urlToString(const QUrl &url)
0175 {
0176     if(!FileAccess::isLocal(url))
0177         return url.toString();
0178 
0179     QString result = url.toLocalFile();
0180     if(result.isEmpty())
0181         return url.path();
0182 
0183     return result;
0184 }
0185