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
ignore_result(const T &)14 template <typename T> static void ignore_result(const T&) {}
15
CreateStringFlag(const char * name,const char * shortName,SkCommandLineFlags::StringArray * pStrings,const char * defaultValue,const char * helpString,const char * extendedHelpString)16 bool SkFlagInfo::CreateStringFlag(const char* name, const char* shortName,
17 SkCommandLineFlags::StringArray* pStrings,
18 const char* defaultValue, const char* helpString,
19 const char* extendedHelpString) {
20 SkFlagInfo* info = new SkFlagInfo(name, shortName, kString_FlagType, helpString,
21 extendedHelpString);
22 info->fDefaultString.set(defaultValue);
23
24 info->fStrings = pStrings;
25 SetDefaultStrings(pStrings, defaultValue);
26 return true;
27 }
28
SetDefaultStrings(SkCommandLineFlags::StringArray * pStrings,const char * defaultValue)29 void SkFlagInfo::SetDefaultStrings(SkCommandLineFlags::StringArray* pStrings,
30 const char* defaultValue) {
31 pStrings->reset();
32 if (nullptr == defaultValue) {
33 return;
34 }
35 // 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
string_is_in(const char * target,const char * set[],size_t len)59 static 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 */
parse_bool_arg(const char * string,bool * result)75 static 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
match(const char * string)90 bool SkFlagInfo::match(const char* string) {
91 if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
92 string++;
93 const SkString* compareName;
94 if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
95 string++;
96 // There were two dashes. Compare against full name.
97 compareName = &fName;
98 } else {
99 // One dash. Compare against the short name.
100 compareName = &fShortName;
101 }
102 if (kBool_FlagType == fFlagType) {
103 // In this case, go ahead and set the value.
104 if (compareName->equals(string)) {
105 *fBoolValue = true;
106 return true;
107 }
108 if (SkStrStartsWith(string, "no") && strlen(string) > 2) {
109 string += 2;
110 // Only allow "no" to be prepended to the full name.
111 if (fName.equals(string)) {
112 *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);
121 if (flag.equals(*compareName)) {
122 // 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;
126 } else {
127 return false;
128 }
129 }
130 }
131 return compareName->equals(string);
132 } else {
133 // Has no dash
134 return false;
135 }
136 return false;
137 }
138
139 SkFlagInfo* SkCommandLineFlags::gHead;
140 SkString SkCommandLineFlags::gUsage;
141
SetUsage(const char * usage)142 void SkCommandLineFlags::SetUsage(const char* usage) {
143 gUsage.set(usage);
144 }
145
PrintUsage()146 void SkCommandLineFlags::PrintUsage() {
147 SkDebugf("%s", gUsage.c_str());
148 }
149
150 // Maximum line length for the help message.
151 #define LINE_LENGTH 72
152
print_indented(const SkString & text)153 static void print_indented(const SkString& text) {
154 size_t length = text.size();
155 const char* currLine = text.c_str();
156 const char* stop = currLine + length;
157 while (currLine < stop) {
158 int lineBreak = SkStrFind(currLine, "\n");
159 if (lineBreak < 0) {
160 lineBreak = static_cast<int>(strlen(currLine));
161 }
162 if (lineBreak > LINE_LENGTH) {
163 // 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 }
178 SkDebugf(" %.*s\n", spaceIndex, currLine);
179 currLine += spaceIndex + gap;
180 } else {
181 // the line break is within the limit. Break there.
182 lineBreak++;
183 SkDebugf(" %.*s", lineBreak, currLine);
184 currLine += lineBreak;
185 }
186 }
187 }
188
print_help_for_flag(const SkFlagInfo * flag)189 static 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 }
print_extended_help_for_flag(const SkFlagInfo * flag)204 static void print_extended_help_for_flag(const SkFlagInfo* flag) {
205 print_help_for_flag(flag);
206 print_indented(flag->extendedHelp());
207 SkDebugf("\n");
208 }
209
210 namespace {
211 struct CompareFlagsByName {
operator ()__anona22d78850111::CompareFlagsByName212 bool operator()(SkFlagInfo* a, SkFlagInfo* b) const {
213 return strcmp(a->name().c_str(), b->name().c_str()) < 0;
214 }
215 };
216 } // namespace
217
Parse(int argc,const char * const * argv)218 void SkCommandLineFlags::Parse(int argc, const char* const * argv) {
219 // Only allow calling this function once.
220 static bool gOnce;
221 if (gOnce) {
222 SkDebugf("Parse should only be called once at the beginning of main!\n");
223 SkASSERT(false);
224 return;
225 }
226 gOnce = true;
227
228 bool helpPrinted = false;
229 bool flagsPrinted = false;
230 // Loop over argv, starting with 1, since the first is just the name of the program.
231 for (int i = 1; i < argc; i++) {
232 if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
233 // Print help message.
234 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 }
245 if (!flagsPrinted) {
246 SkDebugf("Flags:\n");
247 flagsPrinted = true;
248 }
249 if (0 == helpFlags.count()) {
250 // If no flags followed --help, print them all
251 SkTDArray<SkFlagInfo*> allFlags;
252 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
253 flag = flag->next()) {
254 allFlags.push_back(flag);
255 }
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]);
260 if (allFlags[i]->extendedHelp().size() > 0) {
261 SkDebugf(" Use '--help %s' for more information.\n",
262 allFlags[i]->name().c_str());
263 }
264 }
265 } else {
266 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
267 flag = flag->next()) {
268 for (int k = 0; k < helpFlags.count(); k++) {
269 if (flag->name().equals(helpFlags[k]) ||
270 flag->shortName().equals(helpFlags[k])) {
271 print_extended_help_for_flag(flag);
272 helpFlags.remove(k);
273 break;
274 }
275 }
276 }
277 }
278 if (helpFlags.count() > 0) {
279 SkDebugf("Requested help for unrecognized flags:\n");
280 for (int k = 0; k < helpFlags.count(); k++) {
281 SkDebugf(" --%s\n", helpFlags[k]);
282 }
283 }
284 helpPrinted = true;
285 }
286 if (!helpPrinted) {
287 SkFlagInfo* matchedFlag = nullptr;
288 SkFlagInfo* flag = gHead;
289 int startI = i;
290 while (flag != nullptr) {
291 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 }
299 switch (flag->getFlagType()) {
300 case SkFlagInfo::kBool_FlagType:
301 // 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 }
310 break;
311 case SkFlagInfo::kString_FlagType:
312 flag->resetStrings();
313 // Add all arguments until another flag is reached.
314 while (i+1 < argc) {
315 char* end = nullptr;
316 // Negative numbers aren't flags.
317 ignore_result(strtod(argv[i+1], &end));
318 if (end == argv[i+1] && SkStrStartsWith(argv[i+1], '-')) {
319 break;
320 }
321 i++;
322 flag->append(argv[i]);
323 }
324 break;
325 case SkFlagInfo::kInt_FlagType:
326 i++;
327 flag->setInt(atoi(argv[i]));
328 break;
329 case SkFlagInfo::kUint_FlagType:
330 i++;
331 flag->setUint(strtoul(argv[i], nullptr, 0));
332 break;
333 case SkFlagInfo::kDouble_FlagType:
334 i++;
335 flag->setDouble(atof(argv[i]));
336 break;
337 default:
338 SkDEBUGFAIL("Invalid flag type");
339 }
340 }
341 flag = flag->next();
342 }
343 if (!matchedFlag) {
344 #if defined(SK_BUILD_FOR_MAC)
345 if (SkStrStartsWith(argv[i], "NSDocumentRevisions")
346 || SkStrStartsWith(argv[i], "-NSDocumentRevisions")) {
347 i++; // skip YES
348 } else
349 #endif
350 SkDebugf("Got unknown flag '%s'. Exiting.\n", argv[i]);
351 exit(-1);
352 }
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;
358 gHead = nullptr;
359 while (flag != nullptr) {
360 SkFlagInfo* next = flag->next();
361 delete flag;
362 flag = next;
363 }
364 if (helpPrinted) {
365 exit(0);
366 }
367 }
368
369 namespace {
370
371 template <typename Strings>
ShouldSkipImpl(const Strings & strings,const char * name)372 bool ShouldSkipImpl(const Strings& strings, const char* name) {
373 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
396 : strstr(name, matchName) != nullptr) {
397 return matchExclude;
398 }
399 }
400 return !anyExclude;
401 }
402
403 } // namespace
404
ShouldSkip(const SkTDArray<const char * > & strings,const char * name)405 bool SkCommandLineFlags::ShouldSkip(const SkTDArray<const char*>& strings, const char* name) {
406 return ShouldSkipImpl(strings, name);
407 }
ShouldSkip(const StringArray & strings,const char * name)408 bool SkCommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
409 return ShouldSkipImpl(strings, name);
410 }
411