blob: dc961356926b2be84d3aab416f932a9cdcc8e892 [file] [log] [blame]
/*
* 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/base/SkTDArray.h"
#include "src/base/SkTSort.h"
#include "tools/flags/CommandLineFlags.h"
#include <stdlib.h>
template <typename T> 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, std::size(trueValues))) {
*result = true;
return true;
}
static const char* falseValues[] = {"0", "FALSE", "false"};
if (string_is_in(string, falseValues, std::size(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);
}
// Has no dash
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<int>(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<const char*> helpFlags;
for (int j = i + 1; j < argc; j++) {
if (SkStrStartsWith(argv[j], '-')) {
break;
}
helpFlags.append(1, &argv[j]);
}
if (0 == helpFlags.size()) {
// 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.size()) {
// If no flags followed --help, print them all
SkTDArray<SkFlagInfo*> allFlags;
for (SkFlagInfo* flag = CommandLineFlags::gHead; flag; flag = flag->next()) {
allFlags.push_back(flag);
}
SkTQSort(allFlags.begin(), allFlags.end(), CompareFlagsByName());
for (SkFlagInfo* flag : allFlags) {
print_help_for_flag(flag);
if (flag->extendedHelp().size() > 0) {
SkDebugf(" Use '--help %s' for more information.\n",
flag->name().c_str());
}
}
} else {
for (SkFlagInfo* flag = CommandLineFlags::gHead; flag; flag = flag->next()) {
for (int k = 0; k < helpFlags.size(); k++) {
if (flag->name().equals(helpFlags[k]) ||
flag->shortName().equals(helpFlags[k])) {
print_extended_help_for_flag(flag);
helpFlags.remove(k);
break;
}
}
}
}
if (helpFlags.size() > 0) {
SkDebugf("Requested help for unrecognized flags:\n");
for (int k = 0; k < helpFlags.size(); 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 <typename Strings> bool ShouldSkipImpl(const Strings& strings, const char* name) {
int count = strings.size();
size_t testLen = strlen(name);
bool anyExclude = count == 0;
for (int i = 0; i < strings.size(); ++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<const char*>& strings, const char* name) {
return ShouldSkipImpl(strings, name);
}
bool CommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
return ShouldSkipImpl(strings, name);
}