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 "include/private/SkTDArray.h"
9 #include "src/core/SkTSort.h"
10 #include "tools/flags/CommandLineFlags.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,CommandLineFlags::StringArray * pStrings,const char * defaultValue,const char * helpString,const char * extendedHelpString)16 bool SkFlagInfo::CreateStringFlag(const char* name,
17 const char* shortName,
18 CommandLineFlags::StringArray* pStrings,
19 const char* defaultValue,
20 const char* helpString,
21 const char* extendedHelpString) {
22 SkFlagInfo* info =
23 new SkFlagInfo(name, shortName, kString_FlagType, helpString, extendedHelpString);
24 info->fDefaultString.set(defaultValue);
25
26 info->fStrings = pStrings;
27 SetDefaultStrings(pStrings, defaultValue);
28 return true;
29 }
30
SetDefaultStrings(CommandLineFlags::StringArray * pStrings,const char * defaultValue)31 void SkFlagInfo::SetDefaultStrings(CommandLineFlags::StringArray* pStrings,
32 const char* defaultValue) {
33 pStrings->reset();
34 if (nullptr == defaultValue) {
35 return;
36 }
37 // 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
string_is_in(const char * target,const char * set[],size_t len)61 static 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 */
parse_bool_arg(const char * string,bool * result)77 static 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
match(const char * string)92 bool SkFlagInfo::match(const char* string) {
93 if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
94 string++;
95 const SkString* compareName;
96 if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
97 string++;
98 // There were two dashes. Compare against full name.
99 compareName = &fName;
100 } else {
101 // One dash. Compare against the short name.
102 compareName = &fShortName;
103 }
104 if (kBool_FlagType == fFlagType) {
105 // In this case, go ahead and set the value.
106 if (compareName->equals(string)) {
107 *fBoolValue = true;
108 return true;
109 }
110 if (SkStrStartsWith(string, "no") && strlen(string) > 2) {
111 string += 2;
112 // Only allow "no" to be prepended to the full name.
113 if (fName.equals(string)) {
114 *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);
123 if (flag.equals(*compareName)) {
124 // 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;
128 } else {
129 return false;
130 }
131 }
132 }
133 return compareName->equals(string);
134 } else {
135 // Has no dash
136 return false;
137 }
138 return false;
139 }
140
141 SkFlagInfo* CommandLineFlags::gHead;
142 SkString CommandLineFlags::gUsage;
143
SetUsage(const char * usage)144 void CommandLineFlags::SetUsage(const char* usage) { gUsage.set(usage); }
145
PrintUsage()146 void CommandLineFlags::PrintUsage() { SkDebugf("%s", gUsage.c_str()); }
147
148 // Maximum line length for the help message.
149 #define LINE_LENGTH 72
150
print_indented(const SkString & text)151 static void print_indented(const SkString& text) {
152 size_t length = text.size();
153 const char* currLine = text.c_str();
154 const char* stop = currLine + length;
155 while (currLine < stop) {
156 int lineBreak = SkStrFind(currLine, "\n");
157 if (lineBreak < 0) {
158 lineBreak = static_cast<int>(strlen(currLine));
159 }
160 if (lineBreak > LINE_LENGTH) {
161 // No line break within line length. Will need to insert one.
162 // Find a space before the line break.
163 int spaceIndex = LINE_LENGTH - 1;
164 while (spaceIndex > 0 && currLine[spaceIndex] != ' ') {
165 spaceIndex--;
166 }
167 int gap;
168 if (0 == spaceIndex) {
169 // No spaces on the entire line. Go ahead and break mid word.
170 spaceIndex = LINE_LENGTH;
171 gap = 0;
172 } else {
173 // Skip the space on the next line
174 gap = 1;
175 }
176 SkDebugf(" %.*s\n", spaceIndex, currLine);
177 currLine += spaceIndex + gap;
178 } else {
179 // the line break is within the limit. Break there.
180 lineBreak++;
181 SkDebugf(" %.*s", lineBreak, currLine);
182 currLine += lineBreak;
183 }
184 }
185 }
186
print_help_for_flag(const SkFlagInfo * flag)187 static void print_help_for_flag(const SkFlagInfo* flag) {
188 SkDebugf(" --%s", flag->name().c_str());
189 const SkString& shortName = flag->shortName();
190 if (shortName.size() > 0) {
191 SkDebugf(" or -%s", shortName.c_str());
192 }
193 SkDebugf(":\ttype: %s", flag->typeAsString().c_str());
194 if (flag->defaultValue().size() > 0) {
195 SkDebugf("\tdefault: %s", flag->defaultValue().c_str());
196 }
197 SkDebugf("\n");
198 const SkString& help = flag->help();
199 print_indented(help);
200 SkDebugf("\n");
201 }
print_extended_help_for_flag(const SkFlagInfo * flag)202 static void print_extended_help_for_flag(const SkFlagInfo* flag) {
203 print_help_for_flag(flag);
204 print_indented(flag->extendedHelp());
205 SkDebugf("\n");
206 }
207
208 namespace {
209 struct CompareFlagsByName {
operator ()__anon641cd10f0111::CompareFlagsByName210 bool operator()(SkFlagInfo* a, SkFlagInfo* b) const {
211 return strcmp(a->name().c_str(), b->name().c_str()) < 0;
212 }
213 };
214 } // namespace
215
Parse(int argc,const char * const * argv)216 void CommandLineFlags::Parse(int argc, const char* const* argv) {
217 // Only allow calling this function once.
218 static bool gOnce;
219 if (gOnce) {
220 SkDebugf("Parse should only be called once at the beginning of main!\n");
221 SkASSERT(false);
222 return;
223 }
224 gOnce = true;
225
226 bool helpPrinted = false;
227 bool flagsPrinted = false;
228 // Loop over argv, starting with 1, since the first is just the name of the program.
229 for (int i = 1; i < argc; i++) {
230 if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
231 // Print help message.
232 SkTDArray<const char*> helpFlags;
233 for (int j = i + 1; j < argc; j++) {
234 if (SkStrStartsWith(argv[j], '-')) {
235 break;
236 }
237 helpFlags.append(1, &argv[j]);
238 }
239 if (0 == helpFlags.count()) {
240 // Only print general help message if help for specific flags is not requested.
241 SkDebugf("%s\n%s\n", argv[0], gUsage.c_str());
242 }
243 if (!flagsPrinted) {
244 SkDebugf("Flags:\n");
245 flagsPrinted = true;
246 }
247 if (0 == helpFlags.count()) {
248 // If no flags followed --help, print them all
249 SkTDArray<SkFlagInfo*> allFlags;
250 for (SkFlagInfo* flag = CommandLineFlags::gHead; flag; flag = flag->next()) {
251 allFlags.push_back(flag);
252 }
253 SkTQSort(&allFlags[0], &allFlags[allFlags.count() - 1], CompareFlagsByName());
254 for (int i = 0; i < allFlags.count(); ++i) {
255 print_help_for_flag(allFlags[i]);
256 if (allFlags[i]->extendedHelp().size() > 0) {
257 SkDebugf(" Use '--help %s' for more information.\n",
258 allFlags[i]->name().c_str());
259 }
260 }
261 } else {
262 for (SkFlagInfo* flag = CommandLineFlags::gHead; flag; flag = flag->next()) {
263 for (int k = 0; k < helpFlags.count(); k++) {
264 if (flag->name().equals(helpFlags[k]) ||
265 flag->shortName().equals(helpFlags[k])) {
266 print_extended_help_for_flag(flag);
267 helpFlags.remove(k);
268 break;
269 }
270 }
271 }
272 }
273 if (helpFlags.count() > 0) {
274 SkDebugf("Requested help for unrecognized flags:\n");
275 for (int k = 0; k < helpFlags.count(); k++) {
276 SkDebugf(" --%s\n", helpFlags[k]);
277 }
278 }
279 helpPrinted = true;
280 }
281 if (!helpPrinted) {
282 SkFlagInfo* matchedFlag = nullptr;
283 SkFlagInfo* flag = gHead;
284 int startI = i;
285 while (flag != nullptr) {
286 if (flag->match(argv[startI])) {
287 i = startI;
288 if (matchedFlag) {
289 // Don't redefine the same flag with different types.
290 SkASSERT(matchedFlag->getFlagType() == flag->getFlagType());
291 } else {
292 matchedFlag = flag;
293 }
294 switch (flag->getFlagType()) {
295 case SkFlagInfo::kBool_FlagType:
296 // Can be handled by match, above, but can also be set by the next
297 // string.
298 if (i + 1 < argc && !SkStrStartsWith(argv[i + 1], '-')) {
299 i++;
300 bool value;
301 if (parse_bool_arg(argv[i], &value)) {
302 flag->setBool(value);
303 }
304 }
305 break;
306 case SkFlagInfo::kString_FlagType:
307 flag->resetStrings();
308 // Add all arguments until another flag is reached.
309 while (i + 1 < argc) {
310 char* end = nullptr;
311 // Negative numbers aren't flags.
312 ignore_result(strtod(argv[i + 1], &end));
313 if (end == argv[i + 1] && SkStrStartsWith(argv[i + 1], '-')) {
314 break;
315 }
316 i++;
317 flag->append(argv[i]);
318 }
319 break;
320 case SkFlagInfo::kInt_FlagType:
321 i++;
322 flag->setInt(atoi(argv[i]));
323 break;
324 case SkFlagInfo::kDouble_FlagType:
325 i++;
326 flag->setDouble(atof(argv[i]));
327 break;
328 default: SkDEBUGFAIL("Invalid flag type");
329 }
330 }
331 flag = flag->next();
332 }
333 if (!matchedFlag) {
334 #if defined(SK_BUILD_FOR_MAC)
335 if (SkStrStartsWith(argv[i], "NSDocumentRevisions") ||
336 SkStrStartsWith(argv[i], "-NSDocumentRevisions")) {
337 i++; // skip YES
338 } else
339 #endif
340 SkDebugf("Got unknown flag '%s'. Exiting.\n", argv[i]);
341 exit(-1);
342 }
343 }
344 }
345 // Since all of the flags have been set, release the memory used by each
346 // flag. FLAGS_x can still be used after this.
347 SkFlagInfo* flag = gHead;
348 gHead = nullptr;
349 while (flag != nullptr) {
350 SkFlagInfo* next = flag->next();
351 delete flag;
352 flag = next;
353 }
354 if (helpPrinted) {
355 exit(0);
356 }
357 }
358
359 namespace {
360
ShouldSkipImpl(const Strings & strings,const char * name)361 template <typename Strings> bool ShouldSkipImpl(const Strings& strings, const char* name) {
362 int count = strings.count();
363 size_t testLen = strlen(name);
364 bool anyExclude = count == 0;
365 for (int i = 0; i < strings.count(); ++i) {
366 const char* matchName = strings[i];
367 size_t matchLen = strlen(matchName);
368 bool matchExclude, matchStart, matchEnd;
369 if ((matchExclude = matchName[0] == '~')) {
370 anyExclude = true;
371 matchName++;
372 matchLen--;
373 }
374 if ((matchStart = matchName[0] == '^')) {
375 matchName++;
376 matchLen--;
377 }
378 if ((matchEnd = matchName[matchLen - 1] == '$')) {
379 matchLen--;
380 }
381 if (matchStart
382 ? (!matchEnd || matchLen == testLen) && strncmp(name, matchName, matchLen) == 0
383 : matchEnd
384 ? matchLen <= testLen &&
385 strncmp(name + testLen - matchLen, matchName, matchLen) == 0
386 : strstr(name, matchName) != nullptr) {
387 return matchExclude;
388 }
389 }
390 return !anyExclude;
391 }
392
393 } // namespace
394
ShouldSkip(const SkTDArray<const char * > & strings,const char * name)395 bool CommandLineFlags::ShouldSkip(const SkTDArray<const char*>& strings, const char* name) {
396 return ShouldSkipImpl(strings, name);
397 }
ShouldSkip(const StringArray & strings,const char * name)398 bool CommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
399 return ShouldSkipImpl(strings, name);
400 }
401