blob: 8e38c525af7d5300a63af41598e93de56da5194b [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
Mike Klein135a1b12017-08-15 13:13:59 -040014DEFINE_bool(undefok, false, "Silently ignore unknown flags instead of crashing.");
commit-bot@chromium.orgffc224f2014-03-25 21:00:02 +000015
mtklein19e259b2015-05-04 10:54:48 -070016template <typename T> static void ignore_result(const T&) {}
17
scroggo@google.com58104a92013-04-24 19:25:26 +000018bool SkFlagInfo::CreateStringFlag(const char* name, const char* shortName,
19 SkCommandLineFlags::StringArray* pStrings,
kkinnunen3e980c32015-12-23 01:33:00 -080020 const char* defaultValue, const char* helpString,
21 const char* extendedHelpString) {
22 SkFlagInfo* info = new SkFlagInfo(name, shortName, kString_FlagType, helpString,
23 extendedHelpString);
scroggo@google.com58104a92013-04-24 19:25:26 +000024 info->fDefaultString.set(defaultValue);
25
26 info->fStrings = pStrings;
27 SetDefaultStrings(pStrings, defaultValue);
28 return true;
29}
30
31void SkFlagInfo::SetDefaultStrings(SkCommandLineFlags::StringArray* pStrings,
32 const char* defaultValue) {
33 pStrings->reset();
halcanary96fcdcc2015-08-27 07:41:13 -070034 if (nullptr == defaultValue) {
scroggo@google.comd0419012013-04-24 19:37:52 +000035 return;
36 }
scroggo@google.com58104a92013-04-24 19:25:26 +000037 // If default is "", leave the array empty.
38 size_t defaultLength = strlen(defaultValue);
39 if (defaultLength > 0) {
40 const char* const defaultEnd = defaultValue + defaultLength;
41 const char* begin = defaultValue;
42 while (true) {
43 while (begin < defaultEnd && ' ' == *begin) {
44 begin++;
45 }
46 if (begin < defaultEnd) {
47 const char* end = begin + 1;
48 while (end < defaultEnd && ' ' != *end) {
49 end++;
50 }
51 size_t length = end - begin;
52 pStrings->append(begin, length);
53 begin = end + 1;
54 } else {
55 break;
56 }
57 }
58 }
59}
60
scroggo@google.com5dc4ca12013-03-21 13:10:59 +000061static bool string_is_in(const char* target, const char* set[], size_t len) {
62 for (size_t i = 0; i < len; i++) {
63 if (0 == strcmp(target, set[i])) {
64 return true;
65 }
66 }
67 return false;
68}
69
70/**
71 * Check to see whether string represents a boolean value.
72 * @param string C style string to parse.
73 * @param result Pointer to a boolean which will be set to the value in the string, if the
74 * string represents a boolean.
75 * @param boolean True if the string represents a boolean, false otherwise.
76 */
77static bool parse_bool_arg(const char* string, bool* result) {
78 static const char* trueValues[] = { "1", "TRUE", "true" };
79 if (string_is_in(string, trueValues, SK_ARRAY_COUNT(trueValues))) {
80 *result = true;
81 return true;
82 }
83 static const char* falseValues[] = { "0", "FALSE", "false" };
84 if (string_is_in(string, falseValues, SK_ARRAY_COUNT(falseValues))) {
85 *result = false;
86 return true;
87 }
88 SkDebugf("Parameter \"%s\" not supported.\n", string);
89 return false;
90}
91
92bool SkFlagInfo::match(const char* string) {
93 if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
94 string++;
scroggo@google.com604e0c22013-04-09 21:25:46 +000095 const SkString* compareName;
scroggo@google.com5dc4ca12013-03-21 13:10:59 +000096 if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
97 string++;
scroggo@google.com604e0c22013-04-09 21:25:46 +000098 // There were two dashes. Compare against full name.
99 compareName = &fName;
100 } else {
101 // One dash. Compare against the short name.
102 compareName = &fShortName;
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000103 }
104 if (kBool_FlagType == fFlagType) {
105 // In this case, go ahead and set the value.
scroggo@google.com604e0c22013-04-09 21:25:46 +0000106 if (compareName->equals(string)) {
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000107 *fBoolValue = true;
108 return true;
109 }
110 if (SkStrStartsWith(string, "no") && strlen(string) > 2) {
111 string += 2;
scroggo@google.com604e0c22013-04-09 21:25:46 +0000112 // Only allow "no" to be prepended to the full name.
113 if (fName.equals(string)) {
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000114 *fBoolValue = false;
115 return true;
116 }
117 return false;
118 }
119 int equalIndex = SkStrFind(string, "=");
120 if (equalIndex > 0) {
121 // The string has an equal sign. Check to see if the string matches.
122 SkString flag(string, equalIndex);
scroggo@google.com604e0c22013-04-09 21:25:46 +0000123 if (flag.equals(*compareName)) {
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000124 // Check to see if the remainder beyond the equal sign is true or false:
125 string += equalIndex + 1;
126 parse_bool_arg(string, fBoolValue);
127 return true;
scroggo@google.com604e0c22013-04-09 21:25:46 +0000128 } else {
129 return false;
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000130 }
131 }
132 }
scroggo@google.com604e0c22013-04-09 21:25:46 +0000133 return compareName->equals(string);
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000134 } else {
135 // Has no dash
136 return false;
137 }
138 return false;
139}
140
scroggo@google.comd9ba9a02013-03-21 19:43:15 +0000141SkFlagInfo* SkCommandLineFlags::gHead;
142SkString SkCommandLineFlags::gUsage;
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000143
scroggo@google.comd9ba9a02013-03-21 19:43:15 +0000144void SkCommandLineFlags::SetUsage(const char* usage) {
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000145 gUsage.set(usage);
146}
147
msarett3478f752016-02-12 14:47:09 -0800148void SkCommandLineFlags::PrintUsage() {
149 SkDebugf("%s", gUsage.c_str());
150}
151
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000152// Maximum line length for the help message.
halcanary27523142015-04-27 06:41:35 -0700153#define LINE_LENGTH 72
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000154
kkinnunen3e980c32015-12-23 01:33:00 -0800155static void print_indented(const SkString& text) {
156 size_t length = text.size();
157 const char* currLine = text.c_str();
scroggo@google.com8366df02013-03-20 19:50:41 +0000158 const char* stop = currLine + length;
159 while (currLine < stop) {
rmistry0f515bd2015-12-22 10:22:26 -0800160 int lineBreak = SkStrFind(currLine, "\n");
kkinnunen3e980c32015-12-23 01:33:00 -0800161 if (lineBreak < 0) {
162 lineBreak = static_cast<int>(strlen(currLine));
163 }
164 if (lineBreak > LINE_LENGTH) {
scroggo@google.com8366df02013-03-20 19:50:41 +0000165 // 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 }
halcanary27523142015-04-27 06:41:35 -0700180 SkDebugf(" %.*s\n", spaceIndex, currLine);
scroggo@google.com8366df02013-03-20 19:50:41 +0000181 currLine += spaceIndex + gap;
182 } else {
183 // the line break is within the limit. Break there.
184 lineBreak++;
halcanary27523142015-04-27 06:41:35 -0700185 SkDebugf(" %.*s", lineBreak, currLine);
scroggo@google.com8366df02013-03-20 19:50:41 +0000186 currLine += lineBreak;
187 }
188 }
kkinnunen3e980c32015-12-23 01:33:00 -0800189}
190
191static void print_help_for_flag(const SkFlagInfo* flag) {
192 SkDebugf(" --%s", flag->name().c_str());
193 const SkString& shortName = flag->shortName();
194 if (shortName.size() > 0) {
195 SkDebugf(" or -%s", shortName.c_str());
196 }
197 SkDebugf(":\ttype: %s", flag->typeAsString().c_str());
198 if (flag->defaultValue().size() > 0) {
199 SkDebugf("\tdefault: %s", flag->defaultValue().c_str());
200 }
201 SkDebugf("\n");
202 const SkString& help = flag->help();
203 print_indented(help);
204 SkDebugf("\n");
205}
206static void print_extended_help_for_flag(const SkFlagInfo* flag) {
207 print_help_for_flag(flag);
208 print_indented(flag->extendedHelp());
scroggo@google.com8366df02013-03-20 19:50:41 +0000209 SkDebugf("\n");
210}
211
halcanary03758b82015-01-18 10:39:25 -0800212namespace {
213struct CompareFlagsByName {
214 bool operator()(SkFlagInfo* a, SkFlagInfo* b) const {
215 return strcmp(a->name().c_str(), b->name().c_str()) < 0;
216 }
217};
218} // namespace
219
Cary Clark7265ea32017-09-14 12:12:32 -0400220void SkCommandLineFlags::Parse(int argc, const char* const * argv) {
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000221 // Only allow calling this function once.
222 static bool gOnce;
223 if (gOnce) {
scroggo@google.com604e0c22013-04-09 21:25:46 +0000224 SkDebugf("Parse should only be called once at the beginning of main!\n");
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000225 SkASSERT(false);
226 return;
227 }
228 gOnce = true;
229
230 bool helpPrinted = false;
Cary Clark0933f332017-07-06 08:33:19 -0400231 bool flagsPrinted = false;
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000232 // Loop over argv, starting with 1, since the first is just the name of the program.
233 for (int i = 1; i < argc; i++) {
scroggo@google.com604e0c22013-04-09 21:25:46 +0000234 if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000235 // Print help message.
scroggo@google.com8366df02013-03-20 19:50:41 +0000236 SkTDArray<const char*> helpFlags;
237 for (int j = i + 1; j < argc; j++) {
238 if (SkStrStartsWith(argv[j], '-')) {
239 break;
240 }
241 helpFlags.append(1, &argv[j]);
242 }
243 if (0 == helpFlags.count()) {
244 // Only print general help message if help for specific flags is not requested.
245 SkDebugf("%s\n%s\n", argv[0], gUsage.c_str());
246 }
Cary Clark0933f332017-07-06 08:33:19 -0400247 if (!flagsPrinted) {
248 SkDebugf("Flags:\n");
249 flagsPrinted = true;
250 }
halcanary03758b82015-01-18 10:39:25 -0800251 if (0 == helpFlags.count()) {
scroggo@google.com8366df02013-03-20 19:50:41 +0000252 // If no flags followed --help, print them all
halcanary03758b82015-01-18 10:39:25 -0800253 SkTDArray<SkFlagInfo*> allFlags;
254 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
255 flag = flag->next()) {
256 allFlags.push(flag);
257 }
258 SkTQSort(&allFlags[0], &allFlags[allFlags.count() - 1],
259 CompareFlagsByName());
260 for (int i = 0; i < allFlags.count(); ++i) {
261 print_help_for_flag(allFlags[i]);
kkinnunen3e980c32015-12-23 01:33:00 -0800262 if (allFlags[i]->extendedHelp().size() > 0) {
263 SkDebugf(" Use '--help %s' for more information.\n",
264 allFlags[i]->name().c_str());
265 }
halcanary03758b82015-01-18 10:39:25 -0800266 }
267 } else {
268 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
269 flag = flag->next()) {
scroggo@google.com8366df02013-03-20 19:50:41 +0000270 for (int k = 0; k < helpFlags.count(); k++) {
271 if (flag->name().equals(helpFlags[k]) ||
272 flag->shortName().equals(helpFlags[k])) {
kkinnunen3e980c32015-12-23 01:33:00 -0800273 print_extended_help_for_flag(flag);
scroggo@google.com8366df02013-03-20 19:50:41 +0000274 helpFlags.remove(k);
275 break;
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000276 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000277 }
278 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000279 }
scroggo@google.com8366df02013-03-20 19:50:41 +0000280 if (helpFlags.count() > 0) {
281 SkDebugf("Requested help for unrecognized flags:\n");
282 for (int k = 0; k < helpFlags.count(); k++) {
halcanary27523142015-04-27 06:41:35 -0700283 SkDebugf(" --%s\n", helpFlags[k]);
scroggo@google.com8366df02013-03-20 19:50:41 +0000284 }
285 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000286 helpPrinted = true;
287 }
288 if (!helpPrinted) {
Brian Osman49f57122016-10-24 14:53:08 -0400289 SkFlagInfo* matchedFlag = nullptr;
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000290 SkFlagInfo* flag = gHead;
Brian Osman49f57122016-10-24 14:53:08 -0400291 int startI = i;
halcanary96fcdcc2015-08-27 07:41:13 -0700292 while (flag != nullptr) {
Brian Osman49f57122016-10-24 14:53:08 -0400293 if (flag->match(argv[startI])) {
294 i = startI;
295 if (matchedFlag) {
296 // Don't redefine the same flag with different types.
297 SkASSERT(matchedFlag->getFlagType() == flag->getFlagType());
298 } else {
299 matchedFlag = flag;
300 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000301 switch (flag->getFlagType()) {
302 case SkFlagInfo::kBool_FlagType:
scroggo@google.com5dc4ca12013-03-21 13:10:59 +0000303 // Can be handled by match, above, but can also be set by the next
304 // string.
305 if (i+1 < argc && !SkStrStartsWith(argv[i+1], '-')) {
306 i++;
307 bool value;
308 if (parse_bool_arg(argv[i], &value)) {
309 flag->setBool(value);
310 }
311 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000312 break;
313 case SkFlagInfo::kString_FlagType:
314 flag->resetStrings();
315 // Add all arguments until another flag is reached.
mtkleindfb7da32015-02-14 18:56:31 -0800316 while (i+1 < argc) {
halcanary96fcdcc2015-08-27 07:41:13 -0700317 char* end = nullptr;
mtklein19e259b2015-05-04 10:54:48 -0700318 // Negative numbers aren't flags.
319 ignore_result(strtod(argv[i+1], &end));
mtkleindfb7da32015-02-14 18:56:31 -0800320 if (end == argv[i+1] && SkStrStartsWith(argv[i+1], '-')) {
321 break;
322 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000323 i++;
324 flag->append(argv[i]);
325 }
326 break;
327 case SkFlagInfo::kInt_FlagType:
328 i++;
329 flag->setInt(atoi(argv[i]));
330 break;
331 case SkFlagInfo::kDouble_FlagType:
332 i++;
333 flag->setDouble(atof(argv[i]));
334 break;
335 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000336 SkDEBUGFAIL("Invalid flag type");
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000337 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000338 }
339 flag = flag->next();
340 }
Brian Osman49f57122016-10-24 14:53:08 -0400341 if (!matchedFlag) {
mtklein06343172016-07-28 09:58:44 -0700342#if defined(SK_BUILD_FOR_MAC)
caryclark83ca6282015-06-10 09:31:09 -0700343 if (SkStrStartsWith(argv[i], "NSDocumentRevisions")
344 || SkStrStartsWith(argv[i], "-NSDocumentRevisions")) {
345 i++; // skip YES
caryclarkc8fcafb2015-01-30 12:37:02 -0800346 } else
347#endif
mtklein929f63f2015-04-08 08:30:38 -0700348 if (FLAGS_undefok) {
349 SkDebugf("FYI: ignoring unknown flag '%s'.\n", argv[i]);
350 } else {
351 SkDebugf("Got unknown flag '%s'. Exiting.\n", argv[i]);
commit-bot@chromium.orgffc224f2014-03-25 21:00:02 +0000352 exit(-1);
353 }
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000354 }
355 }
356 }
357 // Since all of the flags have been set, release the memory used by each
358 // flag. FLAGS_x can still be used after this.
359 SkFlagInfo* flag = gHead;
halcanary96fcdcc2015-08-27 07:41:13 -0700360 gHead = nullptr;
361 while (flag != nullptr) {
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000362 SkFlagInfo* next = flag->next();
halcanary385fe4d2015-08-26 13:07:48 -0700363 delete flag;
scroggo@google.com161e1ba2013-03-04 16:41:06 +0000364 flag = next;
365 }
366 if (helpPrinted) {
367 exit(0);
368 }
369}
sglez@google.com586db932013-07-24 17:24:23 +0000370
commit-bot@chromium.orga6f37e72013-08-30 15:52:46 +0000371namespace {
372
373template <typename Strings>
374bool ShouldSkipImpl(const Strings& strings, const char* name) {
sglez@google.com586db932013-07-24 17:24:23 +0000375 int count = strings.count();
376 size_t testLen = strlen(name);
377 bool anyExclude = count == 0;
378 for (int i = 0; i < strings.count(); ++i) {
379 const char* matchName = strings[i];
380 size_t matchLen = strlen(matchName);
381 bool matchExclude, matchStart, matchEnd;
382 if ((matchExclude = matchName[0] == '~')) {
383 anyExclude = true;
384 matchName++;
385 matchLen--;
386 }
387 if ((matchStart = matchName[0] == '^')) {
388 matchName++;
389 matchLen--;
390 }
391 if ((matchEnd = matchName[matchLen - 1] == '$')) {
392 matchLen--;
393 }
394 if (matchStart ? (!matchEnd || matchLen == testLen)
395 && strncmp(name, matchName, matchLen) == 0
396 : matchEnd ? matchLen <= testLen
397 && strncmp(name + testLen - matchLen, matchName, matchLen) == 0
Ben Wagnera93a14a2017-08-28 10:34:05 -0400398 : strstr(name, matchName) != nullptr) {
sglez@google.com586db932013-07-24 17:24:23 +0000399 return matchExclude;
400 }
401 }
402 return !anyExclude;
403}
commit-bot@chromium.orga6f37e72013-08-30 15:52:46 +0000404
405} // namespace
406
407bool SkCommandLineFlags::ShouldSkip(const SkTDArray<const char*>& strings, const char* name) {
408 return ShouldSkipImpl(strings, name);
409}
410bool SkCommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
411 return ShouldSkipImpl(strings, name);
412}