1/*
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"
9#include "SkOSFile.h"
10#include "SkOSPath.h"
11
12static void debug_out(int len, const char* data) {
13    // convenient place to intercept arbitrary output
14    SkDebugf("%.*s", len, data);
15}
16
17bool ParserCommon::parseFile(const char* fileOrPath, const char* suffix) {
18    if (!sk_isdir(fileOrPath)) {
19        if (!this->parseFromFile(fileOrPath)) {
20            SkDebugf("failed to parse %s\n", fileOrPath);
21            return false;
22        }
23    } else {
24        SkOSFile::Iter it(fileOrPath, suffix);
25        for (SkString file; it.next(&file); ) {
26            SkString p = SkOSPath::Join(fileOrPath, file.c_str());
27            const char* hunk = p.c_str();
28            if (!SkStrEndsWith(hunk, suffix)) {
29                continue;
30            }
31            if (!this->parseFromFile(hunk)) {
32                SkDebugf("failed to parse %s\n", hunk);
33                return false;
34            }
35        }
36    }
37    return true;
38}
39
40bool ParserCommon::parseStatus(const char* statusFile, const char* suffix, StatusFilter filter) {
41    StatusIter iter(statusFile, suffix, filter);
42    if (iter.empty()) {
43        return false;
44    }
45    for (string file; iter.next(&file); ) {
46        SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
47        const char* hunk = p.c_str();
48        if (!this->parseFromFile(hunk)) {
49            SkDebugf("failed to parse %s\n", hunk);
50            return false;
51        }
52    }
53    return true;
54}
55
56bool ParserCommon::parseSetup(const char* path) {
57    sk_sp<SkData> data = SkData::MakeFromFileName(path);
58    if (nullptr == data.get()) {
59        SkDebugf("%s missing\n", path);
60        return false;
61    }
62    const char* rawText = (const char*) data->data();
63    bool hasCR = false;
64    size_t dataSize = data->size();
65    for (size_t index = 0; index < dataSize; ++index) {
66        if ('\r' == rawText[index]) {
67            hasCR = true;
68            break;
69        }
70    }
71    string name(path);
72    if (hasCR) {
73        vector<char> lfOnly;
74        for (size_t index = 0; index < dataSize; ++index) {
75            char ch = rawText[index];
76            if ('\r' == rawText[index]) {
77                ch = '\n';
78                if ('\n' == rawText[index + 1]) {
79                    ++index;
80                }
81            }
82            lfOnly.push_back(ch);
83        }
84        fLFOnly[name] = lfOnly;
85        dataSize = lfOnly.size();
86        rawText = &fLFOnly[name].front();
87    }
88    fRawData[name] = data;
89    fStart = rawText;
90    fLine = rawText;
91    fChar = rawText;
92    fEnd = rawText + dataSize;
93    fFileName = string(path);
94    fLineCount = 1;
95    return true;
96}
97
98void ParserCommon::writeBlockIndent(int size, const char* data) {
99    while (size && ' ' >= data[size - 1]) {
100        --size;
101    }
102    bool newLine = false;
103    while (size) {
104        while (size && ' ' > data[0]) {
105            ++data;
106            --size;
107        }
108        if (!size) {
109            return;
110        }
111        if (newLine) {
112            this->lf(1);
113        }
114        TextParser parser(fFileName, data, data + size, fLineCount);
115        const char* lineEnd = parser.strnchr('\n', data + size);
116        int len = lineEnd ? (int) (lineEnd - data) : size;
117        this->writePending();
118        this->indentToColumn(fIndent);
119        if (fDebugOut) {
120            debug_out(len, data);
121        }
122        fprintf(fOut, "%.*s", len, data);
123        size -= len;
124        data += len;
125        newLine = true;
126    }
127}
128
129bool ParserCommon::writeBlockTrim(int size, const char* data) {
130    if (fOutdentNext) {
131        fIndent -= 4;
132        fOutdentNext = false;
133    }
134    while (size && ' ' >= data[0]) {
135        ++data;
136        --size;
137    }
138    while (size && ' ' >= data[size - 1]) {
139        --size;
140    }
141    if (size <= 0) {
142        fLastChar = '\0';
143        return false;
144    }
145    SkASSERT(size < 16000);
146    if (size > 3 && !strncmp("#end", data, 4)) {
147        fMaxLF = 1;
148    }
149    if (this->leadingPunctuation(data, (size_t) size)) {
150        fPendingSpace = 0;
151    }
152    this->writePending();
153    if (fDebugOut) {
154        debug_out(size, data);
155    }
156    fprintf(fOut, "%.*s", size, data);
157    int added = 0;
158    fLastChar = data[size - 1];
159    while (size > 0 && '\n' != data[--size]) {
160        ++added;
161    }
162    fColumn = size ? added : fColumn + added;
163    fSpaces = 0;
164    fLinefeeds = 0;
165    fMaxLF = added > 2 && !strncmp("#if", &data[size + (size > 0)], 3) ? 1 : 2;
166    if (fOutdentNext) {
167        fIndent -= 4;
168        fOutdentNext = false;
169    }
170    return true;
171}
172
173void ParserCommon::writePending() {
174    fPendingLF = SkTMin(fPendingLF, fMaxLF);
175    bool wroteLF = false;
176    while (fLinefeeds < fPendingLF) {
177        if (fDebugOut) {
178            SkDebugf("\n");
179        }
180        fprintf(fOut, "\n");
181        ++fLinefeeds;
182        wroteLF = true;
183    }
184    fPendingLF = 0;
185    if (wroteLF) {
186        SkASSERT(0 == fColumn);
187        SkASSERT(fIndent >= fSpaces);
188        if (fDebugOut) {
189            SkDebugf("%*s", fIndent - fSpaces, "");
190        }
191        fprintf(fOut, "%*s", fIndent - fSpaces, "");
192        fColumn = fIndent;
193        fSpaces = fIndent;
194    }
195    for (int index = 0; index < fPendingSpace; ++index) {
196        if (fDebugOut) {
197            SkDebugf(" ");
198        }
199        fprintf(fOut, " ");
200        ++fColumn;
201    }
202    fPendingSpace = 0;
203}
204
205void ParserCommon::writeString(const char* str) {
206    const size_t len = strlen(str);
207    SkASSERT(len > 0);
208    SkASSERT(' ' < str[0]);
209    fLastChar = str[len - 1];
210    SkASSERT(' ' < fLastChar);
211    SkASSERT(!strchr(str, '\n'));
212    if (this->leadingPunctuation(str, strlen(str))) {
213        fPendingSpace = 0;
214    }
215    this->writePending();
216    if (fDebugOut) {
217        debug_out((int) strlen(str), str);
218    }
219    fprintf(fOut, "%s", str);
220    fColumn += len;
221    fSpaces = 0;
222    fLinefeeds = 0;
223    fMaxLF = 2;
224}
225
226const char* ParserCommon::ReadToBuffer(string filename, int* size) {
227    FILE* file = fopen(filename.c_str(), "rb");
228    if (!file) {
229        return nullptr;
230    }
231    fseek(file, 0L, SEEK_END);
232    *size = (int) ftell(file);
233    rewind(file);
234    char* buffer = new char[*size];
235    memset(buffer, ' ', *size);
236    SkAssertResult(*size == (int)fread(buffer, 1, *size, file));
237    fclose(file);
238    fflush(file);
239    return buffer;
240}
241
242bool ParserCommon::writtenFileDiffers(string filename, string readname) {
243    int writtenSize, readSize;
244    const char* written = ReadToBuffer(filename, &writtenSize);
245    if (!written) {
246        return true;
247    }
248    const char* read = ReadToBuffer(readname, &readSize);
249    if (!read) {
250        delete[] written;
251        return true;
252    }
253#if 0  // enable for debugging this
254    int smaller = SkTMin(writtenSize, readSize);
255    for (int index = 0; index < smaller; ++index) {
256        if (written[index] != read[index]) {
257            SkDebugf("%.*s\n", 40, &written[index]);
258            SkDebugf("%.*s\n", 40, &read[index]);
259            break;
260        }
261    }
262#endif
263    if (readSize != writtenSize) {
264        return true;
265    }
266    bool result = !!memcmp(written, read, writtenSize);
267    delete[] written;
268    delete[] read;
269    return result;
270}
271
272StatusIter::StatusIter(const char* statusFile, const char* suffix, StatusFilter filter)
273    : fSuffix(suffix)
274    , fFilter(filter) {
275    if (!this->parseFromFile(statusFile)) {
276        return;
277    }
278}
279
280static const char* block_names[] = {
281    "Completed",
282    "InProgress",
283};
284
285string StatusIter::baseDir() {
286    SkASSERT(fStack.back().fObject.isArray());
287    SkASSERT(fStack.size() > 2);
288    string dir;
289    for (unsigned index = 2; index < fStack.size(); ++index) {
290        dir += fStack[index].fName;
291        if (index < fStack.size() - 1) {
292            dir += SkOSPath::SEPARATOR;
293        }
294    }
295    return dir;
296}
297
298// FIXME: need to compare fBlockName against fFilter
299// need to compare fSuffix against next value returned
300bool StatusIter::next(string* str) {
301    JsonStatus* status;
302    do {
303        do {
304            if (fStack.empty()) {
305                return false;
306            }
307            status = &fStack.back();
308            if (status->fIter != status->fObject.end()) {
309                break;
310            }
311            fStack.pop_back();
312        } while (true);
313        if (1 == fStack.size()) {
314            do {
315                StatusFilter blockType = StatusFilter::kUnknown;
316                for (unsigned index = 0; index < SK_ARRAY_COUNT(block_names); ++index) {
317                    if (status->fIter.key().asString() == block_names[index]) {
318                        blockType = (StatusFilter) index;
319                        break;
320                    }
321                }
322                if (blockType <= fFilter) {
323                    break;
324                }
325                status->fIter++;
326            } while (status->fIter != status->fObject.end());
327            if (status->fIter == status->fObject.end()) {
328                continue;
329            }
330        }
331        if (!status->fObject.isArray()) {
332            SkASSERT(status->fIter != status->fObject.end());
333            JsonStatus block = {
334                *status->fIter,
335                status->fIter->begin(),
336                status->fIter.key().asString()
337            };
338            fStack.emplace_back(block);
339            status = &(&fStack.back())[-1];
340            status->fIter++;
341            status = &fStack.back();
342            continue;
343        }
344        *str = status->fIter->asString();
345        status->fIter++;
346        if (str->length() - strlen(fSuffix) == str->find(fSuffix)) {
347            return true;
348        }
349    } while (true);
350    return true;
351}
352
353bool StatusIter::parseFromFile(const char* path) {
354    sk_sp<SkData> json(SkData::MakeFromFileName(path));
355    if (!json) {
356        SkDebugf("file %s:\n", path);
357        return this->reportError<bool>("file not readable");
358    }
359    Json::Reader reader;
360    const char* data = (const char*)json->data();
361    if (!reader.parse(data, data+json->size(), fRoot)) {
362        SkDebugf("file %s:\n", path);
363        return this->reportError<bool>("file not parsable");
364    }
365    JsonStatus block = { fRoot, fRoot.begin(), "" };
366    fStack.emplace_back(block);
367    return true;
368}
369
370void StatusIter::reset() {
371    fStack.clear();
372}
373