1/*
2 * Copyright 2013 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 "SkCommandLineFlags.h"
9#include "SkTDArray.h"
10
11DEFINE_string(undefok, "", "Silently ignore unknown flags listed here instead of crashing.");
12
13bool SkFlagInfo::CreateStringFlag(const char* name, const char* shortName,
14                                  SkCommandLineFlags::StringArray* pStrings,
15                                  const char* defaultValue, const char* helpString) {
16    SkFlagInfo* info = SkNEW_ARGS(SkFlagInfo, (name, shortName, kString_FlagType, helpString));
17    info->fDefaultString.set(defaultValue);
18
19    info->fStrings = pStrings;
20    SetDefaultStrings(pStrings, defaultValue);
21    return true;
22}
23
24void SkFlagInfo::SetDefaultStrings(SkCommandLineFlags::StringArray* pStrings,
25                                   const char* defaultValue) {
26    pStrings->reset();
27    if (NULL == defaultValue) {
28        return;
29    }
30    // If default is "", leave the array empty.
31    size_t defaultLength = strlen(defaultValue);
32    if (defaultLength > 0) {
33        const char* const defaultEnd = defaultValue + defaultLength;
34        const char* begin = defaultValue;
35        while (true) {
36            while (begin < defaultEnd && ' ' == *begin) {
37                begin++;
38            }
39            if (begin < defaultEnd) {
40                const char* end = begin + 1;
41                while (end < defaultEnd && ' ' != *end) {
42                    end++;
43                }
44                size_t length = end - begin;
45                pStrings->append(begin, length);
46                begin = end + 1;
47            } else {
48                break;
49            }
50        }
51    }
52}
53
54static bool string_is_in(const char* target, const char* set[], size_t len) {
55    for (size_t i = 0; i < len; i++) {
56        if (0 == strcmp(target, set[i])) {
57            return true;
58        }
59    }
60    return false;
61}
62
63/**
64 *  Check to see whether string represents a boolean value.
65 *  @param string C style string to parse.
66 *  @param result Pointer to a boolean which will be set to the value in the string, if the
67 *      string represents a boolean.
68 *  @param boolean True if the string represents a boolean, false otherwise.
69 */
70static bool parse_bool_arg(const char* string, bool* result) {
71    static const char* trueValues[] = { "1", "TRUE", "true" };
72    if (string_is_in(string, trueValues, SK_ARRAY_COUNT(trueValues))) {
73        *result = true;
74        return true;
75    }
76    static const char* falseValues[] = { "0", "FALSE", "false" };
77    if (string_is_in(string, falseValues, SK_ARRAY_COUNT(falseValues))) {
78        *result = false;
79        return true;
80    }
81    SkDebugf("Parameter \"%s\" not supported.\n", string);
82    return false;
83}
84
85bool SkFlagInfo::match(const char* string) {
86    if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
87        string++;
88        const SkString* compareName;
89        if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
90            string++;
91            // There were two dashes. Compare against full name.
92            compareName = &fName;
93        } else {
94            // One dash. Compare against the short name.
95            compareName = &fShortName;
96        }
97        if (kBool_FlagType == fFlagType) {
98            // In this case, go ahead and set the value.
99            if (compareName->equals(string)) {
100                *fBoolValue = true;
101                return true;
102            }
103            if (SkStrStartsWith(string, "no") && strlen(string) > 2) {
104                string += 2;
105                // Only allow "no" to be prepended to the full name.
106                if (fName.equals(string)) {
107                    *fBoolValue = false;
108                    return true;
109                }
110                return false;
111            }
112            int equalIndex = SkStrFind(string, "=");
113            if (equalIndex > 0) {
114                // The string has an equal sign. Check to see if the string matches.
115                SkString flag(string, equalIndex);
116                if (flag.equals(*compareName)) {
117                    // Check to see if the remainder beyond the equal sign is true or false:
118                    string += equalIndex + 1;
119                    parse_bool_arg(string, fBoolValue);
120                    return true;
121                } else {
122                    return false;
123                }
124            }
125        }
126        return compareName->equals(string);
127    } else {
128        // Has no dash
129        return false;
130    }
131    return false;
132}
133
134SkFlagInfo* SkCommandLineFlags::gHead;
135SkString SkCommandLineFlags::gUsage;
136
137void SkCommandLineFlags::SetUsage(const char* usage) {
138    gUsage.set(usage);
139}
140
141// Maximum line length for the help message.
142#define LINE_LENGTH 80
143
144static void print_help_for_flag(const SkFlagInfo* flag) {
145    SkDebugf("\t--%s", flag->name().c_str());
146    const SkString& shortName = flag->shortName();
147    if (shortName.size() > 0) {
148        SkDebugf(" or -%s", shortName.c_str());
149    }
150    SkDebugf(":\ttype: %s", flag->typeAsString().c_str());
151    if (flag->defaultValue().size() > 0) {
152        SkDebugf("\tdefault: %s", flag->defaultValue().c_str());
153    }
154    SkDebugf("\n");
155    const SkString& help = flag->help();
156    size_t length = help.size();
157    const char* currLine = help.c_str();
158    const char* stop = currLine + length;
159    while (currLine < stop) {
160        if (strlen(currLine) < LINE_LENGTH) {
161            // Only one line length's worth of text left.
162            SkDebugf("\t\t%s\n", currLine);
163            break;
164        }
165        int lineBreak = SkStrFind(currLine, "\n");
166        if (lineBreak < 0 || lineBreak > LINE_LENGTH) {
167            // No line break within line length. Will need to insert one.
168            // Find a space before the line break.
169            int spaceIndex = LINE_LENGTH - 1;
170            while (spaceIndex > 0 && currLine[spaceIndex] != ' ') {
171                spaceIndex--;
172            }
173            int gap;
174            if (0 == spaceIndex) {
175                // No spaces on the entire line. Go ahead and break mid word.
176                spaceIndex = LINE_LENGTH;
177                gap = 0;
178            } else {
179                // Skip the space on the next line
180                gap = 1;
181            }
182            SkDebugf("\t\t%.*s\n", spaceIndex, currLine);
183            currLine += spaceIndex + gap;
184        } else {
185            // the line break is within the limit. Break there.
186            lineBreak++;
187            SkDebugf("\t\t%.*s", lineBreak, currLine);
188            currLine += lineBreak;
189        }
190    }
191    SkDebugf("\n");
192}
193
194void SkCommandLineFlags::Parse(int argc, char** argv) {
195    // Only allow calling this function once.
196    static bool gOnce;
197    if (gOnce) {
198        SkDebugf("Parse should only be called once at the beginning of main!\n");
199        SkASSERT(false);
200        return;
201    }
202    gOnce = true;
203
204    bool helpPrinted = false;
205    // Loop over argv, starting with 1, since the first is just the name of the program.
206    for (int i = 1; i < argc; i++) {
207        if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
208            // Print help message.
209            SkTDArray<const char*> helpFlags;
210            for (int j = i + 1; j < argc; j++) {
211                if (SkStrStartsWith(argv[j], '-')) {
212                    break;
213                }
214                helpFlags.append(1, &argv[j]);
215            }
216            if (0 == helpFlags.count()) {
217                // Only print general help message if help for specific flags is not requested.
218                SkDebugf("%s\n%s\n", argv[0], gUsage.c_str());
219            }
220            SkDebugf("Flags:\n");
221            SkFlagInfo* flag = SkCommandLineFlags::gHead;
222            while (flag != NULL) {
223                // If no flags followed --help, print them all
224                bool printFlag = 0 == helpFlags.count();
225                if (!printFlag) {
226                    for (int k = 0; k < helpFlags.count(); k++) {
227                        if (flag->name().equals(helpFlags[k]) ||
228                            flag->shortName().equals(helpFlags[k])) {
229                            printFlag = true;
230                            helpFlags.remove(k);
231                            break;
232                        }
233                    }
234                }
235                if (printFlag) {
236                    print_help_for_flag(flag);
237                }
238                flag = flag->next();
239            }
240            if (helpFlags.count() > 0) {
241                SkDebugf("Requested help for unrecognized flags:\n");
242                for (int k = 0; k < helpFlags.count(); k++) {
243                    SkDebugf("\t--%s\n", helpFlags[k]);
244                }
245            }
246            helpPrinted = true;
247        }
248        if (!helpPrinted) {
249            bool flagMatched = false;
250            SkFlagInfo* flag = gHead;
251            while (flag != NULL) {
252                if (flag->match(argv[i])) {
253                    flagMatched = true;
254                    switch (flag->getFlagType()) {
255                        case SkFlagInfo::kBool_FlagType:
256                            // Can be handled by match, above, but can also be set by the next
257                            // string.
258                            if (i+1 < argc && !SkStrStartsWith(argv[i+1], '-')) {
259                                i++;
260                                bool value;
261                                if (parse_bool_arg(argv[i], &value)) {
262                                    flag->setBool(value);
263                                }
264                            }
265                            break;
266                        case SkFlagInfo::kString_FlagType:
267                            flag->resetStrings();
268                            // Add all arguments until another flag is reached.
269                            while (i+1 < argc && !SkStrStartsWith(argv[i+1], '-')) {
270                                i++;
271                                flag->append(argv[i]);
272                            }
273                            break;
274                        case SkFlagInfo::kInt_FlagType:
275                            i++;
276                            flag->setInt(atoi(argv[i]));
277                            break;
278                        case SkFlagInfo::kDouble_FlagType:
279                            i++;
280                            flag->setDouble(atof(argv[i]));
281                            break;
282                        default:
283                            SkDEBUGFAIL("Invalid flag type");
284                    }
285                    break;
286                }
287                flag = flag->next();
288            }
289            if (!flagMatched) {
290                SkString stripped(argv[i]);
291                while (stripped.startsWith('-')) {
292                    stripped.remove(0, 1);
293                }
294                if (!FLAGS_undefok.contains(stripped.c_str())) {
295                    SkDebugf("Got unknown flag \"%s\". Exiting.\n", argv[i]);
296                    exit(-1);
297                }
298            }
299        }
300    }
301    // Since all of the flags have been set, release the memory used by each
302    // flag. FLAGS_x can still be used after this.
303    SkFlagInfo* flag = gHead;
304    gHead = NULL;
305    while (flag != NULL) {
306        SkFlagInfo* next = flag->next();
307        SkDELETE(flag);
308        flag = next;
309    }
310    if (helpPrinted) {
311        exit(0);
312    }
313}
314
315namespace {
316
317template <typename Strings>
318bool ShouldSkipImpl(const Strings& strings, const char* name) {
319    int count = strings.count();
320    size_t testLen = strlen(name);
321    bool anyExclude = count == 0;
322    for (int i = 0; i < strings.count(); ++i) {
323        const char* matchName = strings[i];
324        size_t matchLen = strlen(matchName);
325        bool matchExclude, matchStart, matchEnd;
326        if ((matchExclude = matchName[0] == '~')) {
327            anyExclude = true;
328            matchName++;
329            matchLen--;
330        }
331        if ((matchStart = matchName[0] == '^')) {
332            matchName++;
333            matchLen--;
334        }
335        if ((matchEnd = matchName[matchLen - 1] == '$')) {
336            matchLen--;
337        }
338        if (matchStart ? (!matchEnd || matchLen == testLen)
339                && strncmp(name, matchName, matchLen) == 0
340                : matchEnd ? matchLen <= testLen
341                && strncmp(name + testLen - matchLen, matchName, matchLen) == 0
342                : strstr(name, matchName) != 0) {
343            return matchExclude;
344        }
345    }
346    return !anyExclude;
347}
348
349}  // namespace
350
351bool SkCommandLineFlags::ShouldSkip(const SkTDArray<const char*>& strings, const char* name) {
352    return ShouldSkipImpl(strings, name);
353}
354bool SkCommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
355    return ShouldSkipImpl(strings, name);
356}
357