blob: 5be6d851e5ecd77c704acbc7176b73bae58243d5 [file] [log] [blame]
scroggo@google.com161e1ba2013-03-04 16:41:06 +00001/*
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
scroggo@google.comd9ba9a02013-03-21 19:43:15 +00008#include "SkCommandLineFlags.h"
scroggo@google.comb7dbf632013-04-23 15:38:09 +00009#include "SkTDArray.h"
halcanary03758b82015-01-18 10:39:25 -080010#include "SkTSort.h"
scroggo@google.com161e1ba2013-03-04 16:41:06 +000011
bungeman60e0fee2015-08-26 05:15:46 -070012#include <stdlib.h>
13
mtklein19e259b2015-05-04 10:54:48 -070014template <typename T> static void ignore_result(const T&) {}
15
scroggo@google.com58104a92013-04-24 19:25:26 +000016bool SkFlagInfo::CreateStringFlag(const char* name, const char* shortName,
17 SkCommandLineFlags::StringArray* pStrings,
kkinnunen3e980c32015-12-23 01:33:00 -080018 const char* defaultValue, const char* helpString,
19 const char* extendedHelpString) {
20 SkFlagInfo* info = new SkFlagInfo(name, shortName, kString_FlagType, helpString,
21 extendedHelpString);
scroggo@google.com58104a92013-04-24 19:25:26 +000022 info->fDefaultString.set(defaultValue);
23
24 info->fStrings = pStrings;
25 SetDefaultStrings(pStrings, defaultValue);
26 return true;
27}
28
29void SkFlagInfo::SetDefaultStrings(SkCommandLineFlags::StringArray* pStrings,
30 const char* defaultValue) {
31 pStrings->reset();
halcanary96fcdcc2015-08-27 07:41:13 -070032 if (nullptr == defaultValue) {
scroggo@google.comd0419012013-04-24 19:37:52 +000033 return;
34 }
scroggo@google.com58104a92013-04-24 19:25:26 +000035 // If default is "", leave the array empty.
36 size_t defaultLength = strlen(defaultValue);
37 if (defaultLength > 0) {
38 const char* const defaultEnd = defaultValue + defaultLength;
39 const char* begin = defaultValue;
40 while (true) {
41 while (begin < defaultEnd && ' ' == *begin) {
42 begin++;
43 }
44 if (begin < defaultEnd) {
45 const char* end = begin + 1;
46 while (end < defaultEnd && ' ' != *end) {
47 end++;
48 }
49 size_t length = end - begin;
50 pStrings->append(begin, length);
51 begin = end + 1;
52 } else {
53 break;
54 }
55 }
56 }
57}
58
scroggo@google.com5dc4ca12013-03-21 13:10:59 +000059static bool string_is_in(const char* target, const char* set[], size_t len) {
60 for (size_t i = 0; i < len; i++) {
61 if (0 == strcmp(target, set[i])) {
62 return true;
63 }
64 }
65 return false;
66}
67
68/**
69 * Check to see whether string represents a boolean value.
70 * @param string C style string to parse.
71 * @param result Pointer to a boolean which will be set to the value in the string, if the
72 * string represents a boolean.
73 * @param boolean True if the string represents a boolean, false otherwise.
74 */
75static bool parse_bool_arg(const char* string, bool* result) {
76 static const char* trueValues[] = { "1", "TRUE", "true" };
77 if (string_is_in(string, trueValues, SK_ARRAY_COUNT(trueValues))) {
78 *result = true;
79 return true;
80 }
81 static const char* falseValues[] = { "0", "FALSE", "false" };
82 if (string_is_in(string, falseValues, SK_ARRAY_COUNT(falseValues))) {
83 *result = false;
84 return true;
85 }
86 SkDebugf("Parameter \"%s\" not supported.\n", string);
87 return false;
88}
89
90bool SkFlagInfo::match(const char* string) {
91 if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
92 string++;
scroggo@google.com604e0c22013-04-09 21:25:46 +000093 const SkString* compareName;
scroggo@google.com5dc4ca12013-03-21 13:10:59 +000094 if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
95 string++;
scroggo@google.com604e0c22013-04-09 21:25:46 +000096 // There were two dashes. Compare against full name.
97 compareName = &fName;
98 } else {
99 // One dash. Compare against the short name.
100 compareName = &fShortName;
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000101 }
102 if (kBool_FlagType == fFlagType) {
103 // In this case, go ahead and set the value.
scroggo@google.com604e0c22013-04-09 21:25:46 +0000104 if (compareName->equals(string)) {
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000105 *fBoolValue = true;
106 return true;
107 }
108 if (SkStrStartsWith(string, "no") && strlen(string) > 2) {
109 string += 2;
scroggo@google.com604e0c22013-04-09 21:25:46 +0000110 // Only allow "no" to be prepended to the full name.
111 if (fName.equals(string)) {
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000112 *fBoolValue = false;
113 return true;
114 }
115 return false;
116 }
117 int equalIndex = SkStrFind(string, "=");
118 if (equalIndex > 0) {
119 // The string has an equal sign. Check to see if the string matches.
120 SkString flag(string, equalIndex);
scroggo@google.com604e0c22013-04-09 21:25:46 +0000121 if (flag.equals(*compareName)) {
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000122 // Check to see if the remainder beyond the equal sign is true or false:
123 string += equalIndex + 1;
124 parse_bool_arg(string, fBoolValue);
125 return true;
scroggo@google.com604e0c22013-04-09 21:25:46 +0000126 } else {
127 return false;
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000128 }
129 }
130 }
scroggo@google.com604e0c22013-04-09 21:25:46 +0000131 return compareName->equals(string);
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000132 } else {
133 // Has no dash
134 return false;
135 }
136 return false;
137}
138
scroggo@google.comd9ba9a02013-03-21 19:43:15 +0000139SkFlagInfo* SkCommandLineFlags::gHead;
140SkString SkCommandLineFlags::gUsage;
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000141
scroggo@google.comd9ba9a02013-03-21 19:43:15 +0000142void SkCommandLineFlags::SetUsage(const char* usage) {
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000143 gUsage.set(usage);
144}
145
msarett3478f752016-02-12 14:47:09 -0800146void SkCommandLineFlags::PrintUsage() {
147 SkDebugf("%s", gUsage.c_str());
148}
149
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000150// Maximum line length for the help message.
halcanary27523142015-04-27 06:41:35 -0700151#define LINE_LENGTH 72
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000152
kkinnunen3e980c32015-12-23 01:33:00 -0800153static void print_indented(const SkString& text) {
154 size_t length = text.size();
155 const char* currLine = text.c_str();
scroggo@google.com8366df02013-03-20 19:50:41 +0000156 const char* stop = currLine + length;
157 while (currLine < stop) {
rmistry0f515bd2015-12-22 10:22:26 -0800158 int lineBreak = SkStrFind(currLine, "\n");
kkinnunen3e980c32015-12-23 01:33:00 -0800159 if (lineBreak < 0) {
160 lineBreak = static_cast<int>(strlen(currLine));
161 }
162 if (lineBreak > LINE_LENGTH) {
scroggo@google.com8366df02013-03-20 19:50:41 +0000163 // No line break within line length. Will need to insert one.
164 // Find a space before the line break.
165 int spaceIndex = LINE_LENGTH - 1;
166 while (spaceIndex > 0 && currLine[spaceIndex] != ' ') {
167 spaceIndex--;
168 }
169 int gap;
170 if (0 == spaceIndex) {
171 // No spaces on the entire line. Go ahead and break mid word.
172 spaceIndex = LINE_LENGTH;
173 gap = 0;
174 } else {
175 // Skip the space on the next line
176 gap = 1;
177 }
halcanary27523142015-04-27 06:41:35 -0700178 SkDebugf(" %.*s\n", spaceIndex, currLine);
scroggo@google.com8366df02013-03-20 19:50:41 +0000179 currLine += spaceIndex + gap;
180 } else {
181 // the line break is within the limit. Break there.
182 lineBreak++;
halcanary27523142015-04-27 06:41:35 -0700183 SkDebugf(" %.*s", lineBreak, currLine);
scroggo@google.com8366df02013-03-20 19:50:41 +0000184 currLine += lineBreak;
185 }
186 }
kkinnunen3e980c32015-12-23 01:33:00 -0800187}
188
189static void print_help_for_flag(const SkFlagInfo* flag) {
190 SkDebugf(" --%s", flag->name().c_str());
191 const SkString& shortName = flag->shortName();
192 if (shortName.size() > 0) {
193 SkDebugf(" or -%s", shortName.c_str());
194 }
195 SkDebugf(":\ttype: %s", flag->typeAsString().c_str());
196 if (flag->defaultValue().size() > 0) {
197 SkDebugf("\tdefault: %s", flag->defaultValue().c_str());
198 }
199 SkDebugf("\n");
200 const SkString& help = flag->help();
201 print_indented(help);
202 SkDebugf("\n");
203}
204static void print_extended_help_for_flag(const SkFlagInfo* flag) {
205 print_help_for_flag(flag);
206 print_indented(flag->extendedHelp());
scroggo@google.com8366df02013-03-20 19:50:41 +0000207 SkDebugf("\n");
208}
209
halcanary03758b82015-01-18 10:39:25 -0800210namespace {
211struct CompareFlagsByName {
212 bool operator()(SkFlagInfo* a, SkFlagInfo* b) const {
213 return strcmp(a->name().c_str(), b->name().c_str()) < 0;
214 }
215};
216} // namespace
217
Cary Clark7265ea32017-09-14 12:12:32 -0400218void SkCommandLineFlags::Parse(int argc, const char* const * argv) {
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000219 // Only allow calling this function once.
220 static bool gOnce;
221 if (gOnce) {
scroggo@google.com604e0c22013-04-09 21:25:46 +0000222 SkDebugf("Parse should only be called once at the beginning of main!\n");
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000223 SkASSERT(false);
224 return;
225 }
226 gOnce = true;
227
228 bool helpPrinted = false;
Cary Clark0933f332017-07-06 08:33:19 -0400229 bool flagsPrinted = false;
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000230 // Loop over argv, starting with 1, since the first is just the name of the program.
231 for (int i = 1; i < argc; i++) {
scroggo@google.com604e0c22013-04-09 21:25:46 +0000232 if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000233 // Print help message.
scroggo@google.com8366df02013-03-20 19:50:41 +0000234 SkTDArray<const char*> helpFlags;
235 for (int j = i + 1; j < argc; j++) {
236 if (SkStrStartsWith(argv[j], '-')) {
237 break;
238 }
239 helpFlags.append(1, &argv[j]);
240 }
241 if (0 == helpFlags.count()) {
242 // Only print general help message if help for specific flags is not requested.
243 SkDebugf("%s\n%s\n", argv[0], gUsage.c_str());
244 }
Cary Clark0933f332017-07-06 08:33:19 -0400245 if (!flagsPrinted) {
246 SkDebugf("Flags:\n");
247 flagsPrinted = true;
248 }
halcanary03758b82015-01-18 10:39:25 -0800249 if (0 == helpFlags.count()) {
scroggo@google.com8366df02013-03-20 19:50:41 +0000250 // If no flags followed --help, print them all
halcanary03758b82015-01-18 10:39:25 -0800251 SkTDArray<SkFlagInfo*> allFlags;
252 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
253 flag = flag->next()) {
Mike Reed5edcd312018-08-08 11:23:41 -0400254 allFlags.push_back(flag);
halcanary03758b82015-01-18 10:39:25 -0800255 }
256 SkTQSort(&allFlags[0], &allFlags[allFlags.count() - 1],
257 CompareFlagsByName());
258 for (int i = 0; i < allFlags.count(); ++i) {
259 print_help_for_flag(allFlags[i]);
kkinnunen3e980c32015-12-23 01:33:00 -0800260 if (allFlags[i]->extendedHelp().size() > 0) {
261 SkDebugf(" Use '--help %s' for more information.\n",
262 allFlags[i]->name().c_str());
263 }
halcanary03758b82015-01-18 10:39:25 -0800264 }
265 } else {
266 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
267 flag = flag->next()) {
scroggo@google.com8366df02013-03-20 19:50:41 +0000268 for (int k = 0; k < helpFlags.count(); k++) {
269 if (flag->name().equals(helpFlags[k]) ||
270 flag->shortName().equals(helpFlags[k])) {
kkinnunen3e980c32015-12-23 01:33:00 -0800271 print_extended_help_for_flag(flag);
scroggo@google.com8366df02013-03-20 19:50:41 +0000272 helpFlags.remove(k);
273 break;
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000274 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000275 }
276 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000277 }
scroggo@google.com8366df02013-03-20 19:50:41 +0000278 if (helpFlags.count() > 0) {
279 SkDebugf("Requested help for unrecognized flags:\n");
280 for (int k = 0; k < helpFlags.count(); k++) {
halcanary27523142015-04-27 06:41:35 -0700281 SkDebugf(" --%s\n", helpFlags[k]);
scroggo@google.com8366df02013-03-20 19:50:41 +0000282 }
283 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000284 helpPrinted = true;
285 }
286 if (!helpPrinted) {
Brian Osman49f57122016-10-24 14:53:08 -0400287 SkFlagInfo* matchedFlag = nullptr;
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000288 SkFlagInfo* flag = gHead;
Brian Osman49f57122016-10-24 14:53:08 -0400289 int startI = i;
halcanary96fcdcc2015-08-27 07:41:13 -0700290 while (flag != nullptr) {
Brian Osman49f57122016-10-24 14:53:08 -0400291 if (flag->match(argv[startI])) {
292 i = startI;
293 if (matchedFlag) {
294 // Don't redefine the same flag with different types.
295 SkASSERT(matchedFlag->getFlagType() == flag->getFlagType());
296 } else {
297 matchedFlag = flag;
298 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000299 switch (flag->getFlagType()) {
300 case SkFlagInfo::kBool_FlagType:
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000301 // Can be handled by match, above, but can also be set by the next
302 // string.
303 if (i+1 < argc && !SkStrStartsWith(argv[i+1], '-')) {
304 i++;
305 bool value;
306 if (parse_bool_arg(argv[i], &value)) {
307 flag->setBool(value);
308 }
309 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000310 break;
311 case SkFlagInfo::kString_FlagType:
312 flag->resetStrings();
313 // Add all arguments until another flag is reached.
mtkleindfb7da32015-02-14 18:56:31 -0800314 while (i+1 < argc) {
halcanary96fcdcc2015-08-27 07:41:13 -0700315 char* end = nullptr;
mtklein19e259b2015-05-04 10:54:48 -0700316 // Negative numbers aren't flags.
317 ignore_result(strtod(argv[i+1], &end));
mtkleindfb7da32015-02-14 18:56:31 -0800318 if (end == argv[i+1] && SkStrStartsWith(argv[i+1], '-')) {
319 break;
320 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000321 i++;
322 flag->append(argv[i]);
323 }
324 break;
325 case SkFlagInfo::kInt_FlagType:
326 i++;
327 flag->setInt(atoi(argv[i]));
328 break;
Michael Ludwig3ad86d02018-09-28 16:00:38 -0400329 case SkFlagInfo::kUint_FlagType:
330 i++;
331 flag->setUint(strtoul(argv[i], nullptr, 0));
332 break;
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000333 case SkFlagInfo::kDouble_FlagType:
334 i++;
335 flag->setDouble(atof(argv[i]));
336 break;
337 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000338 SkDEBUGFAIL("Invalid flag type");
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000339 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000340 }
341 flag = flag->next();
342 }
Brian Osman49f57122016-10-24 14:53:08 -0400343 if (!matchedFlag) {
mtklein06343172016-07-28 09:58:44 -0700344#if defined(SK_BUILD_FOR_MAC)
caryclark83ca6282015-06-10 09:31:09 -0700345 if (SkStrStartsWith(argv[i], "NSDocumentRevisions")
346 || SkStrStartsWith(argv[i], "-NSDocumentRevisions")) {
347 i++; // skip YES
caryclarkc8fcafb2015-01-30 12:37:02 -0800348 } else
349#endif
Mike Klein97c7cf12019-01-07 15:20:54 -0600350 SkDebugf("Got unknown flag '%s'. Exiting.\n", argv[i]);
351 exit(-1);
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000352 }
353 }
354 }
355 // Since all of the flags have been set, release the memory used by each
356 // flag. FLAGS_x can still be used after this.
357 SkFlagInfo* flag = gHead;
halcanary96fcdcc2015-08-27 07:41:13 -0700358 gHead = nullptr;
359 while (flag != nullptr) {
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000360 SkFlagInfo* next = flag->next();
halcanary385fe4d2015-08-26 13:07:48 -0700361 delete flag;
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000362 flag = next;
363 }
364 if (helpPrinted) {
365 exit(0);
366 }
367}
sglez@google.com586db932013-07-24 17:24:23 +0000368
commit-bot@chromium.orga6f37e72013-08-30 15:52:46 +0000369namespace {
370
371template <typename Strings>
372bool ShouldSkipImpl(const Strings& strings, const char* name) {
sglez@google.com586db932013-07-24 17:24:23 +0000373 int count = strings.count();
374 size_t testLen = strlen(name);
375 bool anyExclude = count == 0;
376 for (int i = 0; i < strings.count(); ++i) {
377 const char* matchName = strings[i];
378 size_t matchLen = strlen(matchName);
379 bool matchExclude, matchStart, matchEnd;
380 if ((matchExclude = matchName[0] == '~')) {
381 anyExclude = true;
382 matchName++;
383 matchLen--;
384 }
385 if ((matchStart = matchName[0] == '^')) {
386 matchName++;
387 matchLen--;
388 }
389 if ((matchEnd = matchName[matchLen - 1] == '$')) {
390 matchLen--;
391 }
392 if (matchStart ? (!matchEnd || matchLen == testLen)
393 && strncmp(name, matchName, matchLen) == 0
394 : matchEnd ? matchLen <= testLen
395 && strncmp(name + testLen - matchLen, matchName, matchLen) == 0
Ben Wagnera93a14a2017-08-28 10:34:05 -0400396 : strstr(name, matchName) != nullptr) {
sglez@google.com586db932013-07-24 17:24:23 +0000397 return matchExclude;
398 }
399 }
400 return !anyExclude;
401}
commit-bot@chromium.orga6f37e72013-08-30 15:52:46 +0000402
403} // namespace
404
405bool SkCommandLineFlags::ShouldSkip(const SkTDArray<const char*>& strings, const char* name) {
406 return ShouldSkipImpl(strings, name);
407}
408bool SkCommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
409 return ShouldSkipImpl(strings, name);
410}