Home | History | Annotate | Download | only in flags
      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 
     14 #if defined(GOOGLE3) && defined(SK_BUILD_FOR_IOS)
     15     // This is defined by //base only for iOS (I don't know why).
     16     DECLARE_bool(undefok)
     17 #else
     18     DEFINE_bool(undefok, false, "Silently ignore unknown flags instead of crashing.");
     19 #endif
     20 
     21 template <typename T> static void ignore_result(const T&) {}
     22 
     23 bool SkFlagInfo::CreateStringFlag(const char* name, const char* shortName,
     24                                   SkCommandLineFlags::StringArray* pStrings,
     25                                   const char* defaultValue, const char* helpString,
     26                                   const char* extendedHelpString) {
     27     SkFlagInfo* info = new SkFlagInfo(name, shortName, kString_FlagType, helpString,
     28                                       extendedHelpString);
     29     info->fDefaultString.set(defaultValue);
     30 
     31     info->fStrings = pStrings;
     32     SetDefaultStrings(pStrings, defaultValue);
     33     return true;
     34 }
     35 
     36 void SkFlagInfo::SetDefaultStrings(SkCommandLineFlags::StringArray* pStrings,
     37                                    const char* defaultValue) {
     38     pStrings->reset();
     39     if (nullptr == defaultValue) {
     40         return;
     41     }
     42     // If default is "", leave the array empty.
     43     size_t defaultLength = strlen(defaultValue);
     44     if (defaultLength > 0) {
     45         const char* const defaultEnd = defaultValue + defaultLength;
     46         const char* begin = defaultValue;
     47         while (true) {
     48             while (begin < defaultEnd && ' ' == *begin) {
     49                 begin++;
     50             }
     51             if (begin < defaultEnd) {
     52                 const char* end = begin + 1;
     53                 while (end < defaultEnd && ' ' != *end) {
     54                     end++;
     55                 }
     56                 size_t length = end - begin;
     57                 pStrings->append(begin, length);
     58                 begin = end + 1;
     59             } else {
     60                 break;
     61             }
     62         }
     63     }
     64 }
     65 
     66 static bool string_is_in(const char* target, const char* set[], size_t len) {
     67     for (size_t i = 0; i < len; i++) {
     68         if (0 == strcmp(target, set[i])) {
     69             return true;
     70         }
     71     }
     72     return false;
     73 }
     74 
     75 /**
     76  *  Check to see whether string represents a boolean value.
     77  *  @param string C style string to parse.
     78  *  @param result Pointer to a boolean which will be set to the value in the string, if the
     79  *      string represents a boolean.
     80  *  @param boolean True if the string represents a boolean, false otherwise.
     81  */
     82 static bool parse_bool_arg(const char* string, bool* result) {
     83     static const char* trueValues[] = { "1", "TRUE", "true" };
     84     if (string_is_in(string, trueValues, SK_ARRAY_COUNT(trueValues))) {
     85         *result = true;
     86         return true;
     87     }
     88     static const char* falseValues[] = { "0", "FALSE", "false" };
     89     if (string_is_in(string, falseValues, SK_ARRAY_COUNT(falseValues))) {
     90         *result = false;
     91         return true;
     92     }
     93     SkDebugf("Parameter \"%s\" not supported.\n", string);
     94     return false;
     95 }
     96 
     97 bool SkFlagInfo::match(const char* string) {
     98     if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
     99         string++;
    100         const SkString* compareName;
    101         if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
    102             string++;
    103             // There were two dashes. Compare against full name.
    104             compareName = &fName;
    105         } else {
    106             // One dash. Compare against the short name.
    107             compareName = &fShortName;
    108         }
    109         if (kBool_FlagType == fFlagType) {
    110             // In this case, go ahead and set the value.
    111             if (compareName->equals(string)) {
    112                 *fBoolValue = true;
    113                 return true;
    114             }
    115             if (SkStrStartsWith(string, "no") && strlen(string) > 2) {
    116                 string += 2;
    117                 // Only allow "no" to be prepended to the full name.
    118                 if (fName.equals(string)) {
    119                     *fBoolValue = false;
    120                     return true;
    121                 }
    122                 return false;
    123             }
    124             int equalIndex = SkStrFind(string, "=");
    125             if (equalIndex > 0) {
    126                 // The string has an equal sign. Check to see if the string matches.
    127                 SkString flag(string, equalIndex);
    128                 if (flag.equals(*compareName)) {
    129                     // Check to see if the remainder beyond the equal sign is true or false:
    130                     string += equalIndex + 1;
    131                     parse_bool_arg(string, fBoolValue);
    132                     return true;
    133                 } else {
    134                     return false;
    135                 }
    136             }
    137         }
    138         return compareName->equals(string);
    139     } else {
    140         // Has no dash
    141         return false;
    142     }
    143     return false;
    144 }
    145 
    146 SkFlagInfo* SkCommandLineFlags::gHead;
    147 SkString SkCommandLineFlags::gUsage;
    148 
    149 void SkCommandLineFlags::SetUsage(const char* usage) {
    150     gUsage.set(usage);
    151 }
    152 
    153 void SkCommandLineFlags::PrintUsage() {
    154     SkDebugf("%s", gUsage.c_str());
    155 }
    156 
    157 // Maximum line length for the help message.
    158 #define LINE_LENGTH 72
    159 
    160 static void print_indented(const SkString& text) {
    161     size_t length = text.size();
    162     const char* currLine = text.c_str();
    163     const char* stop = currLine + length;
    164     while (currLine < stop) {
    165         int lineBreak = SkStrFind(currLine, "\n");
    166         if (lineBreak < 0) {
    167             lineBreak = static_cast<int>(strlen(currLine));
    168         }
    169         if (lineBreak > LINE_LENGTH) {
    170             // No line break within line length. Will need to insert one.
    171             // Find a space before the line break.
    172             int spaceIndex = LINE_LENGTH - 1;
    173             while (spaceIndex > 0 && currLine[spaceIndex] != ' ') {
    174                 spaceIndex--;
    175             }
    176             int gap;
    177             if (0 == spaceIndex) {
    178                 // No spaces on the entire line. Go ahead and break mid word.
    179                 spaceIndex = LINE_LENGTH;
    180                 gap = 0;
    181             } else {
    182                 // Skip the space on the next line
    183                 gap = 1;
    184             }
    185             SkDebugf("        %.*s\n", spaceIndex, currLine);
    186             currLine += spaceIndex + gap;
    187         } else {
    188             // the line break is within the limit. Break there.
    189             lineBreak++;
    190             SkDebugf("        %.*s", lineBreak, currLine);
    191             currLine += lineBreak;
    192         }
    193     }
    194 }
    195 
    196 static void print_help_for_flag(const SkFlagInfo* flag) {
    197     SkDebugf("    --%s", flag->name().c_str());
    198     const SkString& shortName = flag->shortName();
    199     if (shortName.size() > 0) {
    200         SkDebugf(" or -%s", shortName.c_str());
    201     }
    202     SkDebugf(":\ttype: %s", flag->typeAsString().c_str());
    203     if (flag->defaultValue().size() > 0) {
    204         SkDebugf("\tdefault: %s", flag->defaultValue().c_str());
    205     }
    206     SkDebugf("\n");
    207     const SkString& help = flag->help();
    208     print_indented(help);
    209     SkDebugf("\n");
    210 }
    211 static void print_extended_help_for_flag(const SkFlagInfo* flag) {
    212     print_help_for_flag(flag);
    213     print_indented(flag->extendedHelp());
    214     SkDebugf("\n");
    215 }
    216 
    217 namespace {
    218 struct CompareFlagsByName {
    219     bool operator()(SkFlagInfo* a, SkFlagInfo* b) const {
    220         return strcmp(a->name().c_str(), b->name().c_str()) < 0;
    221     }
    222 };
    223 }  // namespace
    224 
    225 void SkCommandLineFlags::Parse(int argc, char** argv) {
    226     // Only allow calling this function once.
    227     static bool gOnce;
    228     if (gOnce) {
    229         SkDebugf("Parse should only be called once at the beginning of main!\n");
    230         SkASSERT(false);
    231         return;
    232     }
    233     gOnce = true;
    234 
    235     bool helpPrinted = false;
    236     bool flagsPrinted = false;
    237     // Loop over argv, starting with 1, since the first is just the name of the program.
    238     for (int i = 1; i < argc; i++) {
    239         if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
    240             // Print help message.
    241             SkTDArray<const char*> helpFlags;
    242             for (int j = i + 1; j < argc; j++) {
    243                 if (SkStrStartsWith(argv[j], '-')) {
    244                     break;
    245                 }
    246                 helpFlags.append(1, &argv[j]);
    247             }
    248             if (0 == helpFlags.count()) {
    249                 // Only print general help message if help for specific flags is not requested.
    250                 SkDebugf("%s\n%s\n", argv[0], gUsage.c_str());
    251             }
    252             if (!flagsPrinted) {
    253                 SkDebugf("Flags:\n");
    254                 flagsPrinted = true;
    255             }
    256             if (0 == helpFlags.count()) {
    257                 // If no flags followed --help, print them all
    258                 SkTDArray<SkFlagInfo*> allFlags;
    259                 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
    260                      flag = flag->next()) {
    261                     allFlags.push(flag);
    262                 }
    263                 SkTQSort(&allFlags[0], &allFlags[allFlags.count() - 1],
    264                          CompareFlagsByName());
    265                 for (int i = 0; i < allFlags.count(); ++i) {
    266                     print_help_for_flag(allFlags[i]);
    267                     if (allFlags[i]->extendedHelp().size() > 0) {
    268                         SkDebugf("        Use '--help %s' for more information.\n",
    269                                  allFlags[i]->name().c_str());
    270                     }
    271                 }
    272             } else {
    273                 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
    274                      flag = flag->next()) {
    275                     for (int k = 0; k < helpFlags.count(); k++) {
    276                         if (flag->name().equals(helpFlags[k]) ||
    277                             flag->shortName().equals(helpFlags[k])) {
    278                             print_extended_help_for_flag(flag);
    279                             helpFlags.remove(k);
    280                             break;
    281                         }
    282                     }
    283                 }
    284             }
    285             if (helpFlags.count() > 0) {
    286                 SkDebugf("Requested help for unrecognized flags:\n");
    287                 for (int k = 0; k < helpFlags.count(); k++) {
    288                     SkDebugf("    --%s\n", helpFlags[k]);
    289                 }
    290             }
    291             helpPrinted = true;
    292         }
    293         if (!helpPrinted) {
    294             SkFlagInfo* matchedFlag = nullptr;
    295             SkFlagInfo* flag = gHead;
    296             int startI = i;
    297             while (flag != nullptr) {
    298                 if (flag->match(argv[startI])) {
    299                     i = startI;
    300                     if (matchedFlag) {
    301                         // Don't redefine the same flag with different types.
    302                         SkASSERT(matchedFlag->getFlagType() == flag->getFlagType());
    303                     } else {
    304                         matchedFlag = flag;
    305                     }
    306                     switch (flag->getFlagType()) {
    307                         case SkFlagInfo::kBool_FlagType:
    308                             // Can be handled by match, above, but can also be set by the next
    309                             // string.
    310                             if (i+1 < argc && !SkStrStartsWith(argv[i+1], '-')) {
    311                                 i++;
    312                                 bool value;
    313                                 if (parse_bool_arg(argv[i], &value)) {
    314                                     flag->setBool(value);
    315                                 }
    316                             }
    317                             break;
    318                         case SkFlagInfo::kString_FlagType:
    319                             flag->resetStrings();
    320                             // Add all arguments until another flag is reached.
    321                             while (i+1 < argc) {
    322                                 char* end = nullptr;
    323                                 // Negative numbers aren't flags.
    324                                 ignore_result(strtod(argv[i+1], &end));
    325                                 if (end == argv[i+1] && SkStrStartsWith(argv[i+1], '-')) {
    326                                     break;
    327                                 }
    328                                 i++;
    329                                 flag->append(argv[i]);
    330                             }
    331                             break;
    332                         case SkFlagInfo::kInt_FlagType:
    333                             i++;
    334                             flag->setInt(atoi(argv[i]));
    335                             break;
    336                         case SkFlagInfo::kDouble_FlagType:
    337                             i++;
    338                             flag->setDouble(atof(argv[i]));
    339                             break;
    340                         default:
    341                             SkDEBUGFAIL("Invalid flag type");
    342                     }
    343                 }
    344                 flag = flag->next();
    345             }
    346             if (!matchedFlag) {
    347 #if defined(SK_BUILD_FOR_MAC)
    348                 if (SkStrStartsWith(argv[i], "NSDocumentRevisions")
    349                         || SkStrStartsWith(argv[i], "-NSDocumentRevisions")) {
    350                     i++;  // skip YES
    351                 } else
    352 #endif
    353                 if (FLAGS_undefok) {
    354                     SkDebugf("FYI: ignoring unknown flag '%s'.\n", argv[i]);
    355                 } else {
    356                     SkDebugf("Got unknown flag '%s'. Exiting.\n", argv[i]);
    357                     exit(-1);
    358                 }
    359             }
    360         }
    361     }
    362     // Since all of the flags have been set, release the memory used by each
    363     // flag. FLAGS_x can still be used after this.
    364     SkFlagInfo* flag = gHead;
    365     gHead = nullptr;
    366     while (flag != nullptr) {
    367         SkFlagInfo* next = flag->next();
    368         delete flag;
    369         flag = next;
    370     }
    371     if (helpPrinted) {
    372         exit(0);
    373     }
    374 }
    375 
    376 namespace {
    377 
    378 template <typename Strings>
    379 bool ShouldSkipImpl(const Strings& strings, const char* name) {
    380     int count = strings.count();
    381     size_t testLen = strlen(name);
    382     bool anyExclude = count == 0;
    383     for (int i = 0; i < strings.count(); ++i) {
    384         const char* matchName = strings[i];
    385         size_t matchLen = strlen(matchName);
    386         bool matchExclude, matchStart, matchEnd;
    387         if ((matchExclude = matchName[0] == '~')) {
    388             anyExclude = true;
    389             matchName++;
    390             matchLen--;
    391         }
    392         if ((matchStart = matchName[0] == '^')) {
    393             matchName++;
    394             matchLen--;
    395         }
    396         if ((matchEnd = matchName[matchLen - 1] == '$')) {
    397             matchLen--;
    398         }
    399         if (matchStart ? (!matchEnd || matchLen == testLen)
    400                 && strncmp(name, matchName, matchLen) == 0
    401                 : matchEnd ? matchLen <= testLen
    402                 && strncmp(name + testLen - matchLen, matchName, matchLen) == 0
    403                 : strstr(name, matchName) != 0) {
    404             return matchExclude;
    405         }
    406     }
    407     return !anyExclude;
    408 }
    409 
    410 }  // namespace
    411 
    412 bool SkCommandLineFlags::ShouldSkip(const SkTDArray<const char*>& strings, const char* name) {
    413     return ShouldSkipImpl(strings, name);
    414 }
    415 bool SkCommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
    416     return ShouldSkipImpl(strings, name);
    417 }
    418