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).
DECLARE_bool(undefok)16 DECLARE_bool(undefok)
17 #else
18 DEFINE_bool(undefok, false, "Silently ignore unknown flags instead of crashing.");
19 #endif
20
21 template <typename T> static void ignore_result(const T&) {}
22
CreateStringFlag(const char * name,const char * shortName,SkCommandLineFlags::StringArray * pStrings,const char * defaultValue,const char * helpString,const char * extendedHelpString)23 bool 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
SetDefaultStrings(SkCommandLineFlags::StringArray * pStrings,const char * defaultValue)36 void 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
string_is_in(const char * target,const char * set[],size_t len)66 static 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 */
parse_bool_arg(const char * string,bool * result)82 static 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
match(const char * string)97 bool 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
146 SkFlagInfo* SkCommandLineFlags::gHead;
147 SkString SkCommandLineFlags::gUsage;
148
SetUsage(const char * usage)149 void SkCommandLineFlags::SetUsage(const char* usage) {
150 gUsage.set(usage);
151 }
152
PrintUsage()153 void SkCommandLineFlags::PrintUsage() {
154 SkDebugf("%s", gUsage.c_str());
155 }
156
157 // Maximum line length for the help message.
158 #define LINE_LENGTH 72
159
print_indented(const SkString & text)160 static 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
print_help_for_flag(const SkFlagInfo * flag)196 static 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 }
print_extended_help_for_flag(const SkFlagInfo * flag)211 static 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
217 namespace {
218 struct CompareFlagsByName {
operator ()__anon4f4669e60111::CompareFlagsByName219 bool operator()(SkFlagInfo* a, SkFlagInfo* b) const {
220 return strcmp(a->name().c_str(), b->name().c_str()) < 0;
221 }
222 };
223 } // namespace
224
Parse(int argc,char ** argv)225 void 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 bool flagsPrinted = false;
237 // Loop over argv, starting with 1, since the first is just the name of the program.
238 for (int i = 1; i < argc; i++) {
239 if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
240 // Print help message.
241 SkTDArray<const char*> helpFlags;
242 for (int j = i + 1; j < argc; j++) {
243 if (SkStrStartsWith(argv[j], '-')) {
244 break;
245 }
246 helpFlags.append(1, &argv[j]);
247 }
248 if (0 == helpFlags.count()) {
249 // Only print general help message if help for specific flags is not requested.
250 SkDebugf("%s\n%s\n", argv[0], gUsage.c_str());
251 }
252 if (!flagsPrinted) {
253 SkDebugf("Flags:\n");
254 flagsPrinted = true;
255 }
256 if (0 == helpFlags.count()) {
257 // If no flags followed --help, print them all
258 SkTDArray<SkFlagInfo*> allFlags;
259 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
260 flag = flag->next()) {
261 allFlags.push(flag);
262 }
263 SkTQSort(&allFlags[0], &allFlags[allFlags.count() - 1],
264 CompareFlagsByName());
265 for (int i = 0; i < allFlags.count(); ++i) {
266 print_help_for_flag(allFlags[i]);
267 if (allFlags[i]->extendedHelp().size() > 0) {
268 SkDebugf(" Use '--help %s' for more information.\n",
269 allFlags[i]->name().c_str());
270 }
271 }
272 } else {
273 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
274 flag = flag->next()) {
275 for (int k = 0; k < helpFlags.count(); k++) {
276 if (flag->name().equals(helpFlags[k]) ||
277 flag->shortName().equals(helpFlags[k])) {
278 print_extended_help_for_flag(flag);
279 helpFlags.remove(k);
280 break;
281 }
282 }
283 }
284 }
285 if (helpFlags.count() > 0) {
286 SkDebugf("Requested help for unrecognized flags:\n");
287 for (int k = 0; k < helpFlags.count(); k++) {
288 SkDebugf(" --%s\n", helpFlags[k]);
289 }
290 }
291 helpPrinted = true;
292 }
293 if (!helpPrinted) {
294 SkFlagInfo* matchedFlag = nullptr;
295 SkFlagInfo* flag = gHead;
296 int startI = i;
297 while (flag != nullptr) {
298 if (flag->match(argv[startI])) {
299 i = startI;
300 if (matchedFlag) {
301 // Don't redefine the same flag with different types.
302 SkASSERT(matchedFlag->getFlagType() == flag->getFlagType());
303 } else {
304 matchedFlag = flag;
305 }
306 switch (flag->getFlagType()) {
307 case SkFlagInfo::kBool_FlagType:
308 // Can be handled by match, above, but can also be set by the next
309 // string.
310 if (i+1 < argc && !SkStrStartsWith(argv[i+1], '-')) {
311 i++;
312 bool value;
313 if (parse_bool_arg(argv[i], &value)) {
314 flag->setBool(value);
315 }
316 }
317 break;
318 case SkFlagInfo::kString_FlagType:
319 flag->resetStrings();
320 // Add all arguments until another flag is reached.
321 while (i+1 < argc) {
322 char* end = nullptr;
323 // Negative numbers aren't flags.
324 ignore_result(strtod(argv[i+1], &end));
325 if (end == argv[i+1] && SkStrStartsWith(argv[i+1], '-')) {
326 break;
327 }
328 i++;
329 flag->append(argv[i]);
330 }
331 break;
332 case SkFlagInfo::kInt_FlagType:
333 i++;
334 flag->setInt(atoi(argv[i]));
335 break;
336 case SkFlagInfo::kDouble_FlagType:
337 i++;
338 flag->setDouble(atof(argv[i]));
339 break;
340 default:
341 SkDEBUGFAIL("Invalid flag type");
342 }
343 }
344 flag = flag->next();
345 }
346 if (!matchedFlag) {
347 #if defined(SK_BUILD_FOR_MAC)
348 if (SkStrStartsWith(argv[i], "NSDocumentRevisions")
349 || SkStrStartsWith(argv[i], "-NSDocumentRevisions")) {
350 i++; // skip YES
351 } else
352 #endif
353 if (FLAGS_undefok) {
354 SkDebugf("FYI: ignoring unknown flag '%s'.\n", argv[i]);
355 } else {
356 SkDebugf("Got unknown flag '%s'. Exiting.\n", argv[i]);
357 exit(-1);
358 }
359 }
360 }
361 }
362 // Since all of the flags have been set, release the memory used by each
363 // flag. FLAGS_x can still be used after this.
364 SkFlagInfo* flag = gHead;
365 gHead = nullptr;
366 while (flag != nullptr) {
367 SkFlagInfo* next = flag->next();
368 delete flag;
369 flag = next;
370 }
371 if (helpPrinted) {
372 exit(0);
373 }
374 }
375
376 namespace {
377
378 template <typename Strings>
ShouldSkipImpl(const Strings & strings,const char * name)379 bool ShouldSkipImpl(const Strings& strings, const char* name) {
380 int count = strings.count();
381 size_t testLen = strlen(name);
382 bool anyExclude = count == 0;
383 for (int i = 0; i < strings.count(); ++i) {
384 const char* matchName = strings[i];
385 size_t matchLen = strlen(matchName);
386 bool matchExclude, matchStart, matchEnd;
387 if ((matchExclude = matchName[0] == '~')) {
388 anyExclude = true;
389 matchName++;
390 matchLen--;
391 }
392 if ((matchStart = matchName[0] == '^')) {
393 matchName++;
394 matchLen--;
395 }
396 if ((matchEnd = matchName[matchLen - 1] == '$')) {
397 matchLen--;
398 }
399 if (matchStart ? (!matchEnd || matchLen == testLen)
400 && strncmp(name, matchName, matchLen) == 0
401 : matchEnd ? matchLen <= testLen
402 && strncmp(name + testLen - matchLen, matchName, matchLen) == 0
403 : strstr(name, matchName) != 0) {
404 return matchExclude;
405 }
406 }
407 return !anyExclude;
408 }
409
410 } // namespace
411
ShouldSkip(const SkTDArray<const char * > & strings,const char * name)412 bool SkCommandLineFlags::ShouldSkip(const SkTDArray<const char*>& strings, const char* name) {
413 return ShouldSkipImpl(strings, name);
414 }
ShouldSkip(const StringArray & strings,const char * name)415 bool SkCommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
416 return ShouldSkipImpl(strings, name);
417 }
418