text: /*
   * 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 <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, 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<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.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<SkFlagInfo*> allFlags;
                  for (SkFlagInfo* flag = CommandLineFlags::gHead; flag; flag = flag->next()) {
                      allFlags.push_back(flag);
                  }
                  SkTQSort(&allFlags[0], &allFlags[allFlags.count() - 1], 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 <typename Strings> 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<const char*>& strings, const char* name) {
      return ShouldSkipImpl(strings, name);
  }
  bool CommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
      return ShouldSkipImpl(strings, name);
  }
  