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