1 /*
2     SPDX-FileCopyrightText: 2016 Anton Anikin <anton.anikin@htower.ru>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "kdevformatfile.h"
8 #include "wildcardhelpers.h"
9 
10 #include <QDir>
11 #include <QFile>
12 #include <QFileInfo>
13 #include <QProcess>
14 
15 #include <utility>
16 
17 namespace KDevelop {
18 
KDevFormatFile(const QString & origFilePath,const QString & tempFilePath)19 KDevFormatFile::KDevFormatFile(const QString& origFilePath, const QString& tempFilePath)
20     : formatFileName{QStringLiteral("format_sources")}
21     , m_origFilePath(origFilePath)
22     , m_tempFilePath(tempFilePath)
23 {
24 }
25 
find()26 bool KDevFormatFile::find()
27 {
28     QDir srcDir(QFileInfo(m_origFilePath).canonicalPath());
29 
30     do {
31         if (srcDir.exists(formatFileName)) {
32             QDir::setCurrent(srcDir.canonicalPath());
33 
34             qStdOut() << "found \""
35                       << QFileInfo(srcDir.canonicalPath() + QDir::separator() + formatFileName).canonicalFilePath()
36                       << "\"\n";
37             return true;
38         }
39     } while (!srcDir.isRoot() && srcDir.cdUp());
40 
41     return false;
42 }
43 
read()44 bool KDevFormatFile::read()
45 {
46     constexpr QChar delimiter = QLatin1Char(':');
47 
48     QFile formatFile(formatFileName);
49     if (!formatFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
50         qStdOut() << "unable to open \"" << formatFileName << "\"\n";
51         return false;
52     }
53 
54     int lineNumber = 0;
55     while (!formatFile.atEnd()) {
56         ++lineNumber;
57 
58         QString line = QString::fromUtf8(formatFile.readLine().trimmed());
59         if (line.isEmpty() || line.startsWith(QLatin1Char('#')))
60             continue;
61 
62         if (line.indexOf(delimiter) < 0) {
63             // We found the simple syntax without wildcards, and only with the command
64             m_formatLines.append({QStringList{}, std::move(line)});
65         } else {
66             // We found the correct syntax with "wildcards : command"
67 
68 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
69             QStringList wildcards = line.section(delimiter, 0, 0).split(QLatin1Char(' '), Qt::SkipEmptyParts);
70 #else
71             QStringList wildcards = line.section(delimiter, 0, 0).split(QLatin1Char(' '), QString::SkipEmptyParts);
72 #endif
73             QString command = line.section(delimiter, 1).trimmed();
74 
75             if (wildcards.isEmpty()) {
76                 qStdOut() << formatFileName << ":" << lineNumber
77                           << ": error: empty wildcard, skip the line\n";
78                 continue;
79             }
80             m_formatLines.append({std::move(wildcards), std::move(command)});
81         }
82     }
83 
84     if (m_formatLines.isEmpty()) {
85         qStdOut() << formatFileName << ": error: no commands are found\n";
86         return false;
87     }
88 
89     return true;
90 }
91 
apply()92 bool KDevFormatFile::apply()
93 {
94     for (const KDevFormatLine& formatLine : qAsConst(m_formatLines)) {
95         if (formatLine.wildcards.isEmpty()) {
96             qStdOut() << "matched \"" << m_origFilePath << "\" without wildcard";
97             return executeCommand(formatLine.command);
98         }
99 
100         const QChar dirSeparator = QDir::separator();
101         for (const QString& wildcard : formatLine.wildcards) {
102             const QString pattern = QDir::current().canonicalPath() + dirSeparator + wildcard.trimmed();
103             if (WildcardHelpers::matchSinglePattern(pattern, m_origFilePath)) {
104                 qStdOut() << "matched \"" << m_origFilePath << "\" with wildcard \"" << wildcard << '\"';
105                 return executeCommand(formatLine.command);
106             }
107         }
108     }
109 
110     qStdOut() << formatFileName << ": error: no commands applicable to \"" << m_origFilePath << "\"\n";
111     return false;
112 }
113 
executeCommand(QString command)114 bool KDevFormatFile::executeCommand(QString command)
115 {
116     if (command.isEmpty()) {
117         qStdOut() << ", empty command => nothing to do\n";
118         return true;
119     }
120     qStdOut() << ", using command \"" << command << "\"\n";
121 
122     command.replace(QLatin1String("$ORIGFILE"), m_origFilePath);
123     command.replace(QLatin1String("$TMPFILE"), m_tempFilePath);
124 
125 #ifdef Q_OS_WIN
126     const QString interpreter = QStringLiteral("cmd");
127     const QStringList arguments{QStringLiteral("/c"), command};
128 #else
129     const QString interpreter = QStringLiteral("sh");
130     const QStringList arguments{QStringLiteral("-c"), command};
131 #endif
132     const int execResult = QProcess::execute(interpreter, arguments);
133 
134     if (execResult != 0) {
135         const QString interpreterDescription = QLatin1String("interpreter ") + interpreter;
136         if (execResult == -2) {
137             qStdOut() << "error: " << interpreterDescription << " failed to start\n";
138             return false;
139         }
140         if (execResult == -1) {
141             qStdOut() << "error: " << interpreterDescription << " crashed\n";
142             return false;
143         }
144         qStdOut() << "warning: " << interpreterDescription << " exited with code " << execResult << '\n';
145     }
146 
147     return true;
148 }
149 
150 }
151