blob: 33bf14918ea94c54b4f4aee55831e58cb857daed [file] [log] [blame]
Cary Clark8032b982017-07-28 11:04:54 -04001/*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "bookmaker.h"
Cary Clarka560c472017-11-27 10:44:06 -05009#include "SkOSFile.h"
10#include "SkOSPath.h"
11
Cary Clark154beea2017-10-26 07:58:48 -040012static void debug_out(int len, const char* data) {
13 // convenient place to intercept arbitrary output
14 SkDebugf("%.*s", len, data);
15}
16
Cary Clark186d08f2018-04-03 08:43:27 -040017bool ParserCommon::parseFile(const char* fileOrPath, const char* suffix, OneFile oneFile) {
Cary Clarka560c472017-11-27 10:44:06 -050018 if (!sk_isdir(fileOrPath)) {
19 if (!this->parseFromFile(fileOrPath)) {
20 SkDebugf("failed to parse %s\n", fileOrPath);
21 return false;
22 }
Cary Clark186d08f2018-04-03 08:43:27 -040023 } else if (OneFile::kNo == oneFile) {
Cary Clarka560c472017-11-27 10:44:06 -050024 SkOSFile::Iter it(fileOrPath, suffix);
25 for (SkString file; it.next(&file); ) {
Cary Clark4dc5a452018-05-21 11:56:57 -040026 // FIXME: skip difficult file for now
27 if (string::npos != string(file.c_str()).find("SkFontArguments")) {
28 continue;
29 }
30 if (string::npos != string(file.c_str()).find("SkFontStyle")) {
31 continue;
32 }
Cary Clarka560c472017-11-27 10:44:06 -050033 SkString p = SkOSPath::Join(fileOrPath, file.c_str());
34 const char* hunk = p.c_str();
35 if (!SkStrEndsWith(hunk, suffix)) {
36 continue;
37 }
38 if (!this->parseFromFile(hunk)) {
39 SkDebugf("failed to parse %s\n", hunk);
40 return false;
41 }
42 }
43 }
44 return true;
45}
46
Cary Clark2f466242017-12-11 16:03:17 -050047bool ParserCommon::parseStatus(const char* statusFile, const char* suffix, StatusFilter filter) {
48 StatusIter iter(statusFile, suffix, filter);
49 if (iter.empty()) {
50 return false;
51 }
52 for (string file; iter.next(&file); ) {
53 SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
54 const char* hunk = p.c_str();
55 if (!this->parseFromFile(hunk)) {
56 SkDebugf("failed to parse %s\n", hunk);
57 return false;
58 }
59 }
60 return true;
61}
Cary Clarka560c472017-11-27 10:44:06 -050062
Cary Clark8032b982017-07-28 11:04:54 -040063bool ParserCommon::parseSetup(const char* path) {
Cary Clark8032b982017-07-28 11:04:54 -040064 sk_sp<SkData> data = SkData::MakeFromFileName(path);
65 if (nullptr == data.get()) {
66 SkDebugf("%s missing\n", path);
67 return false;
68 }
69 const char* rawText = (const char*) data->data();
70 bool hasCR = false;
71 size_t dataSize = data->size();
72 for (size_t index = 0; index < dataSize; ++index) {
73 if ('\r' == rawText[index]) {
74 hasCR = true;
75 break;
76 }
77 }
78 string name(path);
79 if (hasCR) {
80 vector<char> lfOnly;
81 for (size_t index = 0; index < dataSize; ++index) {
82 char ch = rawText[index];
83 if ('\r' == rawText[index]) {
84 ch = '\n';
85 if ('\n' == rawText[index + 1]) {
86 ++index;
87 }
88 }
89 lfOnly.push_back(ch);
90 }
91 fLFOnly[name] = lfOnly;
92 dataSize = lfOnly.size();
93 rawText = &fLFOnly[name].front();
94 }
95 fRawData[name] = data;
96 fStart = rawText;
97 fLine = rawText;
98 fChar = rawText;
99 fEnd = rawText + dataSize;
100 fFileName = string(path);
101 fLineCount = 1;
102 return true;
103}
Cary Clark154beea2017-10-26 07:58:48 -0400104
Cary Clark137b8742018-05-30 09:21:49 -0400105bool ParserCommon::writeBlockIndent(int size, const char* data) {
106 bool wroteSomething = false;
Cary Clark154beea2017-10-26 07:58:48 -0400107 while (size && ' ' >= data[size - 1]) {
108 --size;
109 }
110 bool newLine = false;
111 while (size) {
112 while (size && ' ' > data[0]) {
113 ++data;
114 --size;
115 }
116 if (!size) {
Cary Clark137b8742018-05-30 09:21:49 -0400117 return wroteSomething;
Cary Clark154beea2017-10-26 07:58:48 -0400118 }
Cary Clark0d225392018-06-07 09:59:07 -0400119 if (fReturnOnWrite) {
120 return true;
121 }
Cary Clark154beea2017-10-26 07:58:48 -0400122 if (newLine) {
123 this->lf(1);
124 }
125 TextParser parser(fFileName, data, data + size, fLineCount);
126 const char* lineEnd = parser.strnchr('\n', data + size);
127 int len = lineEnd ? (int) (lineEnd - data) : size;
128 this->writePending();
129 this->indentToColumn(fIndent);
130 if (fDebugOut) {
131 debug_out(len, data);
132 }
133 fprintf(fOut, "%.*s", len, data);
134 size -= len;
135 data += len;
136 newLine = true;
Cary Clark137b8742018-05-30 09:21:49 -0400137 wroteSomething = true;
Cary Clark154beea2017-10-26 07:58:48 -0400138 }
Cary Clark137b8742018-05-30 09:21:49 -0400139 return wroteSomething;
Cary Clark154beea2017-10-26 07:58:48 -0400140}
141
142bool ParserCommon::writeBlockTrim(int size, const char* data) {
Cary Clark0d225392018-06-07 09:59:07 -0400143 SkASSERT(size >= 0);
144 if (!fReturnOnWrite && fOutdentNext) {
Cary Clark154beea2017-10-26 07:58:48 -0400145 fIndent -= 4;
146 fOutdentNext = false;
147 }
148 while (size && ' ' >= data[0]) {
149 ++data;
150 --size;
151 }
152 while (size && ' ' >= data[size - 1]) {
153 --size;
154 }
155 if (size <= 0) {
Cary Clark0d225392018-06-07 09:59:07 -0400156 if (!fReturnOnWrite) {
157 fLastChar = '\0';
158 }
Cary Clark154beea2017-10-26 07:58:48 -0400159 return false;
160 }
Cary Clark0d225392018-06-07 09:59:07 -0400161 if (fReturnOnWrite) {
162 return true;
163 }
Ruiqi Mao94d57c42018-07-02 15:20:10 -0400164 SkASSERT(size < 20000);
Cary Clark154beea2017-10-26 07:58:48 -0400165 if (size > 3 && !strncmp("#end", data, 4)) {
166 fMaxLF = 1;
167 }
168 if (this->leadingPunctuation(data, (size_t) size)) {
169 fPendingSpace = 0;
170 }
171 this->writePending();
172 if (fDebugOut) {
173 debug_out(size, data);
174 }
175 fprintf(fOut, "%.*s", size, data);
Cary Clark682c58d2018-05-16 07:07:07 -0400176 fWroteSomething = true;
Cary Clark154beea2017-10-26 07:58:48 -0400177 int added = 0;
178 fLastChar = data[size - 1];
179 while (size > 0 && '\n' != data[--size]) {
180 ++added;
181 }
182 fColumn = size ? added : fColumn + added;
183 fSpaces = 0;
184 fLinefeeds = 0;
185 fMaxLF = added > 2 && !strncmp("#if", &data[size + (size > 0)], 3) ? 1 : 2;
186 if (fOutdentNext) {
187 fIndent -= 4;
188 fOutdentNext = false;
189 }
190 return true;
191}
192
193void ParserCommon::writePending() {
Cary Clark0d225392018-06-07 09:59:07 -0400194 SkASSERT(!fReturnOnWrite);
Cary Clark154beea2017-10-26 07:58:48 -0400195 fPendingLF = SkTMin(fPendingLF, fMaxLF);
196 bool wroteLF = false;
197 while (fLinefeeds < fPendingLF) {
198 if (fDebugOut) {
199 SkDebugf("\n");
200 }
201 fprintf(fOut, "\n");
202 ++fLinefeeds;
203 wroteLF = true;
204 }
205 fPendingLF = 0;
206 if (wroteLF) {
207 SkASSERT(0 == fColumn);
208 SkASSERT(fIndent >= fSpaces);
209 if (fDebugOut) {
210 SkDebugf("%*s", fIndent - fSpaces, "");
211 }
212 fprintf(fOut, "%*s", fIndent - fSpaces, "");
213 fColumn = fIndent;
214 fSpaces = fIndent;
215 }
216 for (int index = 0; index < fPendingSpace; ++index) {
217 if (fDebugOut) {
218 SkDebugf(" ");
219 }
220 fprintf(fOut, " ");
221 ++fColumn;
222 }
223 fPendingSpace = 0;
224}
225
226void ParserCommon::writeString(const char* str) {
Cary Clark0d225392018-06-07 09:59:07 -0400227 SkASSERT(!fReturnOnWrite);
Cary Clark154beea2017-10-26 07:58:48 -0400228 const size_t len = strlen(str);
229 SkASSERT(len > 0);
230 SkASSERT(' ' < str[0]);
231 fLastChar = str[len - 1];
232 SkASSERT(' ' < fLastChar);
233 SkASSERT(!strchr(str, '\n'));
234 if (this->leadingPunctuation(str, strlen(str))) {
235 fPendingSpace = 0;
236 }
237 this->writePending();
238 if (fDebugOut) {
239 debug_out((int) strlen(str), str);
240 }
241 fprintf(fOut, "%s", str);
242 fColumn += len;
243 fSpaces = 0;
244 fLinefeeds = 0;
245 fMaxLF = 2;
246}
Cary Clark2f466242017-12-11 16:03:17 -0500247
Cary Clark27dddae2018-06-08 15:57:37 -0400248char* ParserCommon::ReadToBuffer(string filename, int* size) {
Cary Clark7cfcbca2018-01-04 16:11:51 -0500249 FILE* file = fopen(filename.c_str(), "rb");
250 if (!file) {
251 return nullptr;
252 }
253 fseek(file, 0L, SEEK_END);
254 *size = (int) ftell(file);
255 rewind(file);
256 char* buffer = new char[*size];
257 memset(buffer, ' ', *size);
258 SkAssertResult(*size == (int)fread(buffer, 1, *size, file));
259 fclose(file);
260 fflush(file);
261 return buffer;
262}
263
Cary Clark27dddae2018-06-08 15:57:37 -0400264char* ParserCommon::FindDateTime(char* buffer, int size) {
265 int index = -1;
266 int lineCount = 8;
267 while (++index < size && ('\n' != buffer[index] || --lineCount))
268 ;
269 if (lineCount) {
270 return nullptr;
271 }
272 if (strncmp("\n on 20", &buffer[index], 9)) {
273 return nullptr;
274 }
275 return &buffer[index];
276}
277
Cary Clark7cfcbca2018-01-04 16:11:51 -0500278bool ParserCommon::writtenFileDiffers(string filename, string readname) {
279 int writtenSize, readSize;
Cary Clark27dddae2018-06-08 15:57:37 -0400280 char* written = ReadToBuffer(filename, &writtenSize);
Cary Clark7cfcbca2018-01-04 16:11:51 -0500281 if (!written) {
282 return true;
283 }
Cary Clark27dddae2018-06-08 15:57:37 -0400284 char* read = ReadToBuffer(readname, &readSize);
Cary Clark7cfcbca2018-01-04 16:11:51 -0500285 if (!read) {
286 delete[] written;
287 return true;
288 }
289#if 0 // enable for debugging this
290 int smaller = SkTMin(writtenSize, readSize);
291 for (int index = 0; index < smaller; ++index) {
292 if (written[index] != read[index]) {
293 SkDebugf("%.*s\n", 40, &written[index]);
294 SkDebugf("%.*s\n", 40, &read[index]);
295 break;
296 }
297 }
298#endif
299 if (readSize != writtenSize) {
300 return true;
301 }
Cary Clark27dddae2018-06-08 15:57:37 -0400302 // force the date/time to be the same, if present in both
303 const char* newDateTime = FindDateTime(written, writtenSize);
304 char* oldDateTime = FindDateTime(read, readSize);
305 if (newDateTime && oldDateTime) {
306 memcpy(oldDateTime, newDateTime, 26);
307 }
Cary Clark7cfcbca2018-01-04 16:11:51 -0500308 bool result = !!memcmp(written, read, writtenSize);
309 delete[] written;
310 delete[] read;
311 return result;
312}
313
Cary Clark2f466242017-12-11 16:03:17 -0500314StatusIter::StatusIter(const char* statusFile, const char* suffix, StatusFilter filter)
315 : fSuffix(suffix)
316 , fFilter(filter) {
317 if (!this->parseFromFile(statusFile)) {
318 return;
319 }
320}
321
322static const char* block_names[] = {
323 "Completed",
324 "InProgress",
325};
326
327string StatusIter::baseDir() {
328 SkASSERT(fStack.back().fObject.isArray());
329 SkASSERT(fStack.size() > 2);
330 string dir;
331 for (unsigned index = 2; index < fStack.size(); ++index) {
332 dir += fStack[index].fName;
333 if (index < fStack.size() - 1) {
334 dir += SkOSPath::SEPARATOR;
335 }
336 }
337 return dir;
338}
339
340// FIXME: need to compare fBlockName against fFilter
341// need to compare fSuffix against next value returned
342bool StatusIter::next(string* str) {
343 JsonStatus* status;
344 do {
345 do {
346 if (fStack.empty()) {
347 return false;
348 }
349 status = &fStack.back();
350 if (status->fIter != status->fObject.end()) {
351 break;
352 }
353 fStack.pop_back();
354 } while (true);
355 if (1 == fStack.size()) {
356 do {
357 StatusFilter blockType = StatusFilter::kUnknown;
358 for (unsigned index = 0; index < SK_ARRAY_COUNT(block_names); ++index) {
359 if (status->fIter.key().asString() == block_names[index]) {
360 blockType = (StatusFilter) index;
361 break;
362 }
363 }
364 if (blockType <= fFilter) {
365 break;
366 }
367 status->fIter++;
368 } while (status->fIter != status->fObject.end());
369 if (status->fIter == status->fObject.end()) {
370 continue;
371 }
372 }
373 if (!status->fObject.isArray()) {
374 SkASSERT(status->fIter != status->fObject.end());
375 JsonStatus block = {
376 *status->fIter,
377 status->fIter->begin(),
378 status->fIter.key().asString()
379 };
380 fStack.emplace_back(block);
381 status = &(&fStack.back())[-1];
382 status->fIter++;
383 status = &fStack.back();
384 continue;
385 }
386 *str = status->fIter->asString();
387 status->fIter++;
388 if (str->length() - strlen(fSuffix) == str->find(fSuffix)) {
389 return true;
390 }
391 } while (true);
392 return true;
393}
394
395bool StatusIter::parseFromFile(const char* path) {
396 sk_sp<SkData> json(SkData::MakeFromFileName(path));
397 if (!json) {
398 SkDebugf("file %s:\n", path);
399 return this->reportError<bool>("file not readable");
400 }
401 Json::Reader reader;
402 const char* data = (const char*)json->data();
403 if (!reader.parse(data, data+json->size(), fRoot)) {
404 SkDebugf("file %s:\n", path);
405 return this->reportError<bool>("file not parsable");
406 }
407 JsonStatus block = { fRoot, fRoot.begin(), "" };
408 fStack.emplace_back(block);
409 return true;
410}
411
412void StatusIter::reset() {
413 fStack.clear();
414}