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