1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt for Python.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28
29 #include "fileout.h"
30 #include "messages.h"
31 #include "reporthandler.h"
32
33 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
34 # include <QtCore/QTextCodec>
35 #endif
36 #include <QtCore/QFileInfo>
37 #include <QtCore/QDir>
38 #include <QtCore/QDebug>
39
40 #include <cstdio>
41
42 bool FileOut::dummy = false;
43 bool FileOut::diff = false;
44
45 #ifdef Q_OS_LINUX
46 static const char colorDelete[] = "\033[31m";
47 static const char colorAdd[] = "\033[32m";
48 static const char colorInfo[] = "\033[36m";
49 static const char colorReset[] = "\033[0m";
50 #else
51 static const char colorDelete[] = "";
52 static const char colorAdd[] = "";
53 static const char colorInfo[] = "";
54 static const char colorReset[] = "";
55 #endif
56
FileOut(QString n)57 FileOut::FileOut(QString n) :
58 name(std::move(n)),
59 stream(&tmp),
60 isDone(false)
61 {
62 }
63
~FileOut()64 FileOut::~FileOut()
65 {
66 if (!isDone)
67 done();
68 }
69
lcsLength(const QByteArrayList & a,const QByteArrayList & b)70 static QVector<int> lcsLength(const QByteArrayList &a, const QByteArrayList &b)
71 {
72 const int height = a.size() + 1;
73 const int width = b.size() + 1;
74
75 QVector<int> res(width * height, 0);
76
77 for (int row = 1; row < height; row++) {
78 for (int col = 1; col < width; col++) {
79 if (a[row-1] == b[col-1])
80 res[width * row + col] = res[width * (row-1) + col-1] + 1;
81 else
82 res[width * row + col] = qMax(res[width * row + col-1],
83 res[width * (row-1) + col]);
84 }
85 }
86 return res;
87 }
88
89 enum Type {
90 Add,
91 Delete,
92 Unchanged
93 };
94
95 struct Unit
96 {
97 Type type;
98 int start;
99 int end;
100
101 void print(const QByteArrayList &a, const QByteArrayList &b) const;
102 };
103
print(const QByteArrayList & a,const QByteArrayList & b) const104 void Unit::print(const QByteArrayList &a, const QByteArrayList &b) const
105 {
106 switch (type) {
107 case Unchanged:
108 if ((end - start) > 9) {
109 for (int i = start; i <= start + 2; i++)
110 std::printf(" %s\n", a.at(i).constData());
111 std::printf("%s=\n= %d more lines\n=%s\n",
112 colorInfo, end - start - 6, colorReset);
113 for (int i = end - 2; i <= end; i++)
114 std::printf(" %s\n", a.at(i).constData());
115 } else {
116 for (int i = start; i <= end; i++)
117 std::printf(" %s\n", a.at(i).constData());
118 }
119 break;
120 case Add:
121 std::fputs(colorAdd, stdout);
122 for (int i = start; i <= end; i++)
123 std::printf("+ %s\n", b.at(i).constData());
124 std::fputs(colorReset, stdout);
125 break;
126 case Delete:
127 std::fputs(colorDelete, stdout);
128 for (int i = start; i <= end; i++)
129 std::printf("- %s\n", a.at(i).constData());
130 std::fputs(colorReset, stdout);
131 break;
132 }
133 }
134
unitAppend(Type type,int pos,QVector<Unit> * units)135 static void unitAppend(Type type, int pos, QVector<Unit> *units)
136 {
137 if (!units->isEmpty() && units->last().type == type)
138 units->last().end = pos;
139 else
140 units->append(Unit{type, pos, pos});
141 }
142
diffHelper(const QVector<int> & lcs,const QByteArrayList & a,const QByteArrayList & b,int row,int col)143 static QVector<Unit> diffHelper(const QVector<int> &lcs,
144 const QByteArrayList &a, const QByteArrayList &b,
145 int row, int col)
146 {
147 if (row > 0 && col > 0 && a.at(row - 1) == b.at(col - 1)) {
148 QVector<Unit> result = diffHelper(lcs, a, b, row - 1, col - 1);
149 unitAppend(Unchanged, row - 1, &result);
150 return result;
151 }
152
153 const int width = b.size() + 1;
154 if (col > 0
155 && (row == 0 || lcs.at(width * row + col -1 ) >= lcs.at(width * (row - 1) + col))) {
156 QVector<Unit> result = diffHelper(lcs, a, b, row, col - 1);
157 unitAppend(Add, col - 1, &result);
158 return result;
159 }
160 if (row > 0
161 && (col == 0 || lcs.at(width * row + col-1) < lcs.at(width * (row - 1) + col))) {
162 QVector<Unit> result = diffHelper(lcs, a, b, row - 1, col);
163 unitAppend(Delete, row - 1, &result);
164 return result;
165 }
166 return QVector<Unit>{};
167 }
168
diff(const QByteArrayList & a,const QByteArrayList & b)169 static void diff(const QByteArrayList &a, const QByteArrayList &b)
170 {
171 const QVector<Unit> res = diffHelper(lcsLength(a, b), a, b, a.size(), b.size());
172 for (const Unit &unit : res)
173 unit.print(a, b);
174 }
175
done()176 FileOut::State FileOut::done()
177 {
178 QString errorMessage;
179 const State result = done(&errorMessage);
180 if (result == Failure)
181 qCWarning(lcShiboken, "%s", qPrintable(errorMessage));
182 return result;
183 }
184
done(QString * errorMessage)185 FileOut::State FileOut::done(QString *errorMessage)
186 {
187 Q_ASSERT(!isDone);
188 if (name.isEmpty())
189 return Failure;
190
191 isDone = true;
192 bool fileEqual = false;
193 QFile fileRead(name);
194 QFileInfo info(fileRead);
195 stream.flush();
196 QByteArray original;
197 if (info.exists() && (diff || (info.size() == tmp.size()))) {
198 if (!fileRead.open(QIODevice::ReadOnly)) {
199 *errorMessage = msgCannotOpenForReading(fileRead);
200 return Failure;
201 }
202
203 original = fileRead.readAll();
204 fileRead.close();
205 fileEqual = (original == tmp);
206 }
207
208 if (fileEqual)
209 return Unchanged;
210
211 if (!FileOut::dummy) {
212 QDir dir(info.absolutePath());
213 if (!dir.mkpath(dir.absolutePath())) {
214 *errorMessage = QStringLiteral("unable to create directory '%1'")
215 .arg(QDir::toNativeSeparators(dir.absolutePath()));
216 return Failure;
217 }
218
219 QFile fileWrite(name);
220 if (!fileWrite.open(QIODevice::WriteOnly)) {
221 *errorMessage = msgCannotOpenForWriting(fileWrite);
222 return Failure;
223 }
224 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
225 QTextCodec *codec = QTextCodec::codecForName("UTF-8");
226 stream.setCodec(codec);
227 #endif
228 stream.setDevice(&fileWrite);
229 stream << tmp;
230 }
231 if (diff) {
232 std::printf("%sFile: %s%s\n", colorInfo, qPrintable(name), colorReset);
233 ::diff(original.split('\n'), tmp.split('\n'));
234 std::printf("\n");
235 }
236
237 return Success;
238 }
239
touchFile(const QString & filePath)240 void FileOut::touchFile(const QString &filePath)
241 {
242 QFile toucher(filePath);
243 qint64 size = toucher.size();
244 if (!toucher.open(QIODevice::ReadWrite)) {
245 qCWarning(lcShiboken).noquote().nospace()
246 << QStringLiteral("Failed to touch file '%1'")
247 .arg(QDir::toNativeSeparators(filePath));
248 return;
249 }
250 toucher.resize(size+1);
251 toucher.resize(size);
252 toucher.close();
253 }
254