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