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