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_ANDROID) || defined(SK_BUILD_FOR_IOS))
     15     // I don't know why, but this is defined by //base only for non-Linux.
     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     // Loop over argv, starting with 1, since the first is just the name of the program.
    237     for (int i = 1; i < argc; i++) {
    238         if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
    239             // Print help message.
    240             SkTDArray<const char*> helpFlags;
    241             for (int j = i + 1; j < argc; j++) {
    242                 if (SkStrStartsWith(argv[j], '-')) {
    243                     break;
    244                 }
    245                 helpFlags.append(1, &argv[j]);
    246             }
    247             if (0 == helpFlags.count()) {
    248                 // Only print general help message if help for specific flags is not requested.
    249                 SkDebugf("%s\n%s\n", argv[0], gUsage.c_str());
    250             }
    251             SkDebugf("Flags:\n");
    252 
    253             if (0 == helpFlags.count()) {
    254                 // If no flags followed --help, print them all
    255                 SkTDArray<SkFlagInfo*> allFlags;
    256                 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
    257                      flag = flag->next()) {
    258                     allFlags.push(flag);
    259                 }
    260                 SkTQSort(&allFlags[0], &allFlags[allFlags.count() - 1],
    261                          CompareFlagsByName());
    262                 for (int i = 0; i < allFlags.count(); ++i) {
    263                     print_help_for_flag(allFlags[i]);
    264                     if (allFlags[i]->extendedHelp().size() > 0) {
    265                         SkDebugf("        Use '--help %s' for more information.\n",
    266                                  allFlags[i]->name().c_str());
    267                     }
    268                 }
    269             } else {
    270                 for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
    271                      flag = flag->next()) {
    272                     for (int k = 0; k < helpFlags.count(); k++) {
    273                         if (flag->name().equals(helpFlags[k]) ||
    274                             flag->shortName().equals(helpFlags[k])) {
    275                             print_extended_help_for_flag(flag);
    276                             helpFlags.remove(k);
    277                             break;
    278                         }
    279                     }
    280                 }
    281             }
    282             if (helpFlags.count() > 0) {
    283                 SkDebugf("Requested help for unrecognized flags:\n");
    284                 for (int k = 0; k < helpFlags.count(); k++) {
    285                     SkDebugf("    --%s\n", helpFlags[k]);
    286                 }
    287             }
    288             helpPrinted = true;
    289         }
    290         if (!helpPrinted) {
    291             bool flagMatched = false;
    292             SkFlagInfo* flag = gHead;
    293             while (flag != nullptr) {
    294                 if (flag->match(argv[i])) {
    295                     flagMatched = true;
    296                     switch (flag->getFlagType()) {
    297                         case SkFlagInfo::kBool_FlagType:
    298                             // Can be handled by match, above, but can also be set by the next
    299                             // string.
    300                             if (i+1 < argc && !SkStrStartsWith(argv[i+1], '-')) {
    301                                 i++;
    302                                 bool value;
    303                                 if (parse_bool_arg(argv[i], &value)) {
    304                                     flag->setBool(value);
    305                                 }
    306                             }
    307                             break;
    308                         case SkFlagInfo::kString_FlagType:
    309                             flag->resetStrings();
    310                             // Add all arguments until another flag is reached.
    311                             while (i+1 < argc) {
    312                                 char* end = nullptr;
    313                                 // Negative numbers aren't flags.
    314                                 ignore_result(strtod(argv[i+1], &end));
    315                                 if (end == argv[i+1] && SkStrStartsWith(argv[i+1], '-')) {
    316                                     break;
    317                                 }
    318                                 i++;
    319                                 flag->append(argv[i]);
    320                             }
    321                             break;
    322                         case SkFlagInfo::kInt_FlagType:
    323                             i++;
    324                             flag->setInt(atoi(argv[i]));
    325                             break;
    326                         case SkFlagInfo::kDouble_FlagType:
    327                             i++;
    328                             flag->setDouble(atof(argv[i]));
    329                             break;
    330                         default:
    331                             SkDEBUGFAIL("Invalid flag type");
    332                     }
    333                     break;
    334                 }
    335                 flag = flag->next();
    336             }
    337             if (!flagMatched) {
    338 #if SK_BUILD_FOR_MAC
    339                 if (SkStrStartsWith(argv[i], "NSDocumentRevisions")
    340                         || SkStrStartsWith(argv[i], "-NSDocumentRevisions")) {
    341                     i++;  // skip YES
    342                 } else
    343 #endif
    344                 if (FLAGS_undefok) {
    345                     SkDebugf("FYI: ignoring unknown flag '%s'.\n", argv[i]);
    346                 } else {
    347                     SkDebugf("Got unknown flag '%s'. Exiting.\n", argv[i]);
    348                     exit(-1);
    349                 }
    350             }
    351         }
    352     }
    353     // Since all of the flags have been set, release the memory used by each
    354     // flag. FLAGS_x can still be used after this.
    355     SkFlagInfo* flag = gHead;
    356     gHead = nullptr;
    357     while (flag != nullptr) {
    358         SkFlagInfo* next = flag->next();
    359         delete flag;
    360         flag = next;
    361     }
    362     if (helpPrinted) {
    363         exit(0);
    364     }
    365 }
    366 
    367 namespace {
    368 
    369 template <typename Strings>
    370 bool ShouldSkipImpl(const Strings& strings, const char* name) {
    371     int count = strings.count();
    372     size_t testLen = strlen(name);
    373     bool anyExclude = count == 0;
    374     for (int i = 0; i < strings.count(); ++i) {
    375         const char* matchName = strings[i];
    376         size_t matchLen = strlen(matchName);
    377         bool matchExclude, matchStart, matchEnd;
    378         if ((matchExclude = matchName[0] == '~')) {
    379             anyExclude = true;
    380             matchName++;
    381             matchLen--;
    382         }
    383         if ((matchStart = matchName[0] == '^')) {
    384             matchName++;
    385             matchLen--;
    386         }
    387         if ((matchEnd = matchName[matchLen - 1] == '$')) {
    388             matchLen--;
    389         }
    390         if (matchStart ? (!matchEnd || matchLen == testLen)
    391                 && strncmp(name, matchName, matchLen) == 0
    392                 : matchEnd ? matchLen <= testLen
    393                 && strncmp(name + testLen - matchLen, matchName, matchLen) == 0
    394                 : strstr(name, matchName) != 0) {
    395             return matchExclude;
    396         }
    397     }
    398     return !anyExclude;
    399 }
    400 
    401 }  // namespace
    402 
    403 bool SkCommandLineFlags::ShouldSkip(const SkTDArray<const char*>& strings, const char* name) {
    404     return ShouldSkipImpl(strings, name);
    405 }
    406 bool SkCommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
    407     return ShouldSkipImpl(strings, name);
    408 }
    409