/* * Copyright 2013 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/private/SkTDArray.h" #include "src/core/SkTSort.h" #include "tools/flags/CommandLineFlags.h" #include template static void ignore_result(const T&) {} bool SkFlagInfo::CreateStringFlag(const char* name, const char* shortName, CommandLineFlags::StringArray* pStrings, const char* defaultValue, const char* helpString, const char* extendedHelpString) { SkFlagInfo* info = new SkFlagInfo(name, shortName, kString_FlagType, helpString, extendedHelpString); info->fDefaultString.set(defaultValue); info->fStrings = pStrings; SetDefaultStrings(pStrings, defaultValue); return true; } void SkFlagInfo::SetDefaultStrings(CommandLineFlags::StringArray* pStrings, const char* defaultValue) { pStrings->reset(); if (nullptr == defaultValue) { return; } // If default is "", leave the array empty. size_t defaultLength = strlen(defaultValue); if (defaultLength > 0) { const char* const defaultEnd = defaultValue + defaultLength; const char* begin = defaultValue; while (true) { while (begin < defaultEnd && ' ' == *begin) { begin++; } if (begin < defaultEnd) { const char* end = begin + 1; while (end < defaultEnd && ' ' != *end) { end++; } size_t length = end - begin; pStrings->append(begin, length); begin = end + 1; } else { break; } } } } static bool string_is_in(const char* target, const char* set[], size_t len) { for (size_t i = 0; i < len; i++) { if (0 == strcmp(target, set[i])) { return true; } } return false; } /** * Check to see whether string represents a boolean value. * @param string C style string to parse. * @param result Pointer to a boolean which will be set to the value in the string, if the * string represents a boolean. * @param boolean True if the string represents a boolean, false otherwise. */ static bool parse_bool_arg(const char* string, bool* result) { static const char* trueValues[] = {"1", "TRUE", "true"}; if (string_is_in(string, trueValues, SK_ARRAY_COUNT(trueValues))) { *result = true; return true; } static const char* falseValues[] = {"0", "FALSE", "false"}; if (string_is_in(string, falseValues, SK_ARRAY_COUNT(falseValues))) { *result = false; return true; } SkDebugf("Parameter \"%s\" not supported.\n", string); return false; } bool SkFlagInfo::match(const char* string) { if (SkStrStartsWith(string, '-') && strlen(string) > 1) { string++; const SkString* compareName; if (SkStrStartsWith(string, '-') && strlen(string) > 1) { string++; // There were two dashes. Compare against full name. compareName = &fName; } else { // One dash. Compare against the short name. compareName = &fShortName; } if (kBool_FlagType == fFlagType) { // In this case, go ahead and set the value. if (compareName->equals(string)) { *fBoolValue = true; return true; } if (SkStrStartsWith(string, "no") && strlen(string) > 2) { string += 2; // Only allow "no" to be prepended to the full name. if (fName.equals(string)) { *fBoolValue = false; return true; } return false; } int equalIndex = SkStrFind(string, "="); if (equalIndex > 0) { // The string has an equal sign. Check to see if the string matches. SkString flag(string, equalIndex); if (flag.equals(*compareName)) { // Check to see if the remainder beyond the equal sign is true or false: string += equalIndex + 1; parse_bool_arg(string, fBoolValue); return true; } else { return false; } } } return compareName->equals(string); } else { // Has no dash return false; } return false; } SkFlagInfo* CommandLineFlags::gHead; SkString CommandLineFlags::gUsage; void CommandLineFlags::SetUsage(const char* usage) { gUsage.set(usage); } void CommandLineFlags::PrintUsage() { SkDebugf("%s", gUsage.c_str()); } // Maximum line length for the help message. #define LINE_LENGTH 72 static void print_indented(const SkString& text) { size_t length = text.size(); const char* currLine = text.c_str(); const char* stop = currLine + length; while (currLine < stop) { int lineBreak = SkStrFind(currLine, "\n"); if (lineBreak < 0) { lineBreak = static_cast(strlen(currLine)); } if (lineBreak > LINE_LENGTH) { // No line break within line length. Will need to insert one. // Find a space before the line break. int spaceIndex = LINE_LENGTH - 1; while (spaceIndex > 0 && currLine[spaceIndex] != ' ') { spaceIndex--; } int gap; if (0 == spaceIndex) { // No spaces on the entire line. Go ahead and break mid word. spaceIndex = LINE_LENGTH; gap = 0; } else { // Skip the space on the next line gap = 1; } SkDebugf(" %.*s\n", spaceIndex, currLine); currLine += spaceIndex + gap; } else { // the line break is within the limit. Break there. lineBreak++; SkDebugf(" %.*s", lineBreak, currLine); currLine += lineBreak; } } } static void print_help_for_flag(const SkFlagInfo* flag) { SkDebugf(" --%s", flag->name().c_str()); const SkString& shortName = flag->shortName(); if (shortName.size() > 0) { SkDebugf(" or -%s", shortName.c_str()); } SkDebugf(":\ttype: %s", flag->typeAsString().c_str()); if (flag->defaultValue().size() > 0) { SkDebugf("\tdefault: %s", flag->defaultValue().c_str()); } SkDebugf("\n"); const SkString& help = flag->help(); print_indented(help); SkDebugf("\n"); } static void print_extended_help_for_flag(const SkFlagInfo* flag) { print_help_for_flag(flag); print_indented(flag->extendedHelp()); SkDebugf("\n"); } namespace { struct CompareFlagsByName { bool operator()(SkFlagInfo* a, SkFlagInfo* b) const { return strcmp(a->name().c_str(), b->name().c_str()) < 0; } }; } // namespace void CommandLineFlags::Parse(int argc, const char* const* argv) { // Only allow calling this function once. static bool gOnce; if (gOnce) { SkDebugf("Parse should only be called once at the beginning of main!\n"); SkASSERT(false); return; } gOnce = true; bool helpPrinted = false; bool flagsPrinted = false; // Loop over argv, starting with 1, since the first is just the name of the program. for (int i = 1; i < argc; i++) { if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) { // Print help message. SkTDArray helpFlags; for (int j = i + 1; j < argc; j++) { if (SkStrStartsWith(argv[j], '-')) { break; } helpFlags.append(1, &argv[j]); } if (0 == helpFlags.count()) { // Only print general help message if help for specific flags is not requested. SkDebugf("%s\n%s\n", argv[0], gUsage.c_str()); } if (!flagsPrinted) { SkDebugf("Flags:\n"); flagsPrinted = true; } if (0 == helpFlags.count()) { // If no flags followed --help, print them all SkTDArray allFlags; for (SkFlagInfo* flag = CommandLineFlags::gHead; flag; flag = flag->next()) { allFlags.push_back(flag); } SkTQSort(allFlags.begin(), allFlags.end(), CompareFlagsByName()); for (int i = 0; i < allFlags.count(); ++i) { print_help_for_flag(allFlags[i]); if (allFlags[i]->extendedHelp().size() > 0) { SkDebugf(" Use '--help %s' for more information.\n", allFlags[i]->name().c_str()); } } } else { for (SkFlagInfo* flag = CommandLineFlags::gHead; flag; flag = flag->next()) { for (int k = 0; k < helpFlags.count(); k++) { if (flag->name().equals(helpFlags[k]) || flag->shortName().equals(helpFlags[k])) { print_extended_help_for_flag(flag); helpFlags.remove(k); break; } } } } if (helpFlags.count() > 0) { SkDebugf("Requested help for unrecognized flags:\n"); for (int k = 0; k < helpFlags.count(); k++) { SkDebugf(" --%s\n", helpFlags[k]); } } helpPrinted = true; } if (!helpPrinted) { SkFlagInfo* matchedFlag = nullptr; SkFlagInfo* flag = gHead; int startI = i; while (flag != nullptr) { if (flag->match(argv[startI])) { i = startI; if (matchedFlag) { // Don't redefine the same flag with different types. SkASSERT(matchedFlag->getFlagType() == flag->getFlagType()); } else { matchedFlag = flag; } switch (flag->getFlagType()) { case SkFlagInfo::kBool_FlagType: // Can be handled by match, above, but can also be set by the next // string. if (i + 1 < argc && !SkStrStartsWith(argv[i + 1], '-')) { i++; bool value; if (parse_bool_arg(argv[i], &value)) { flag->setBool(value); } } break; case SkFlagInfo::kString_FlagType: flag->resetStrings(); // Add all arguments until another flag is reached. while (i + 1 < argc) { char* end = nullptr; // Negative numbers aren't flags. ignore_result(strtod(argv[i + 1], &end)); if (end == argv[i + 1] && SkStrStartsWith(argv[i + 1], '-')) { break; } i++; flag->append(argv[i]); } break; case SkFlagInfo::kInt_FlagType: i++; flag->setInt(atoi(argv[i])); break; case SkFlagInfo::kDouble_FlagType: i++; flag->setDouble(atof(argv[i])); break; default: SkDEBUGFAIL("Invalid flag type"); } } flag = flag->next(); } if (!matchedFlag) { #if defined(SK_BUILD_FOR_MAC) if (SkStrStartsWith(argv[i], "NSDocumentRevisions") || SkStrStartsWith(argv[i], "-NSDocumentRevisions")) { i++; // skip YES } else #endif SkDebugf("Got unknown flag '%s'. Exiting.\n", argv[i]); exit(-1); } } } // Since all of the flags have been set, release the memory used by each // flag. FLAGS_x can still be used after this. SkFlagInfo* flag = gHead; gHead = nullptr; while (flag != nullptr) { SkFlagInfo* next = flag->next(); delete flag; flag = next; } if (helpPrinted) { exit(0); } } namespace { template bool ShouldSkipImpl(const Strings& strings, const char* name) { int count = strings.count(); size_t testLen = strlen(name); bool anyExclude = count == 0; for (int i = 0; i < strings.count(); ++i) { const char* matchName = strings[i]; size_t matchLen = strlen(matchName); bool matchExclude, matchStart, matchEnd; if ((matchExclude = matchName[0] == '~')) { anyExclude = true; matchName++; matchLen--; } if ((matchStart = matchName[0] == '^')) { matchName++; matchLen--; } if ((matchEnd = matchName[matchLen - 1] == '$')) { matchLen--; } if (matchStart ? (!matchEnd || matchLen == testLen) && strncmp(name, matchName, matchLen) == 0 : matchEnd ? matchLen <= testLen && strncmp(name + testLen - matchLen, matchName, matchLen) == 0 : strstr(name, matchName) != nullptr) { return matchExclude; } } return !anyExclude; } } // namespace bool CommandLineFlags::ShouldSkip(const SkTDArray& strings, const char* name) { return ShouldSkipImpl(strings, name); } bool CommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) { return ShouldSkipImpl(strings, name); }