| /* |
| * |
| * Copyright (c) 2020 Project CHIP Authors |
| * Copyright (c) 2017 Nest Labs, Inc. |
| * All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @file |
| * Support functions for parsing command-line arguments. |
| * |
| */ |
| |
| #ifndef __STDC_LIMIT_MACROS |
| #define __STDC_LIMIT_MACROS |
| #endif |
| #ifndef __STDC_FORMAT_MACROS |
| #define __STDC_FORMAT_MACROS |
| #endif |
| |
| #include "CHIPArgParser.hpp" |
| |
| #if CHIP_CONFIG_ENABLE_ARG_PARSER |
| |
| #include <climits> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <getopt.h> |
| #include <inttypes.h> |
| #include <lib/support/SafeInt.h> |
| #include <limits.h> |
| #include <stdarg.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <unistd.h> |
| |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/CHIPMemString.h> |
| #include <lib/support/EnforceFormat.h> |
| #include <lib/support/logging/Constants.h> |
| |
| /* |
| * TODO: Revisit these if and when fabric ID and node ID support has |
| * been integrated into the stack. |
| */ |
| #ifndef CHIP_ARG_PARSER_PARSE_FABRIC_ID |
| #define CHIP_ARG_PARSER_PARSE_FABRIC_ID 0 |
| #endif // CHIP_ARG_PARSER_PARSE_FABRIC_ID |
| |
| namespace chip { |
| namespace ArgParser { |
| |
| using namespace chip; |
| |
| static char * MakeShortOptions(OptionSet ** optSets); |
| static struct option * MakeLongOptions(OptionSet ** optSets); |
| static int32_t SplitArgs(char * argStr, char **& argList, char * initialArg = nullptr); |
| static bool GetNextArg(char *& parsePoint); |
| static size_t CountOptionSets(OptionSet * optSets[]); |
| static size_t CountAllOptions(OptionSet * optSets[]); |
| static void FindOptionByIndex(OptionSet ** optSets, int optIndex, OptionSet *& optSet, OptionDef *& optDef); |
| static void FindOptionById(OptionSet ** optSets, int optId, OptionSet *& optSet, OptionDef *& optDef); |
| static const char ** MakeUniqueHelpGroupNamesList(OptionSet * optSets[]); |
| static void PutStringWithNewLine(FILE * s, const char * str); |
| static void PutStringWithBlankLine(FILE * s, const char * str); |
| #if CHIP_CONFIG_ENABLE_ARG_PARSER_VALIDITY_CHECKS |
| static bool SanityCheckOptions(OptionSet * optSets[]); |
| #endif // CHIP_CONFIG_ENABLE_ARG_PARSER_VALIDITY_CHECKS |
| |
| static inline bool IsShortOptionChar(int ch) |
| { |
| return isgraph(ch); |
| } |
| |
| /** |
| * @brief |
| * The list of OptionSets passed to the currently active ParseArgs() call. |
| * |
| * @details |
| * This value will be NULL when no call to ParseArgs() is in progress. |
| */ |
| OptionSet ** gActiveOptionSets = nullptr; |
| |
| /** |
| * @brief |
| * Pointer to function used to print errors that occur during argument parsing. |
| * |
| * @details |
| * Applications should call PrintArgError() to report errors in their option and |
| * non-option argument handling functions, rather than printing directly to |
| * stdout/stderr. |
| * |
| * Defaults to a pointer to the `DefaultPrintArgError()` function. |
| */ |
| void (*PrintArgError)(const char * msg, ...) = DefaultPrintArgError; |
| |
| /** |
| * @fn bool ParseArgs(const char *progName, int argc, char * const argv[], OptionSet *optSets[], |
| * NonOptionArgHandlerFunct nonOptArgHandler, bool ignoreUnknown) |
| * |
| * @brief |
| * Parse a set of command line-style arguments, calling handling functions to process each |
| * option and non-option argument. |
| * |
| * @param[in] progName The name of the program or context in which the arguments are |
| * being parsed. This string will be used to prefix error |
| * messages and warnings. |
| * @param[in] argc The number of arguments to be parsed, plus 1. |
| * @param[in] argv An array of argument strings to be parsed. The array length must |
| * be 1 greater than the value specified for argc, and |
| * argv[argc] must be set to NULL. Argument parsing begins with the |
| * *second* array element (argv[1]); element 0 is ignored. |
| * @param[in] optSets A list of pointers to `OptionSet` structures that define the legal |
| * options. The supplied list must be terminated with a NULL. |
| * @param[in] nonOptArgHandler A pointer to a function that will be called once option parsing |
| * is complete with any remaining non-option arguments . The function |
| * is called regardless of whether any arguments remain. If a NULL |
| * is passed `ParseArgs()` will report an error if any non-option |
| * arguments are present. |
| * @param[in] ignoreUnknown If true, silently ignore any unrecognized options. |
| * |
| * @return `true` if all options and non-option arguments were parsed |
| * successfully; `false` if an option was unrecognized or if one of |
| * the handler functions failed (i.e. returned false). |
| * |
| * |
| * @details |
| * ParseArgs() takes a list of arguments (`argv`) and parses them according to a set of supplied |
| * option definitions. The function supports both long (--opt) and short (-o) options and implements |
| * the same option syntax as the GNU getopt_long(3) function. |
| * |
| * Option definitions are passed to ParseArgs() as an array of OptionSet structures (`optSets`). |
| * Each OptionSet contains an array of option definitions and a handler function. ParseArgs() |
| * processes option arguments in the given order, calling the respective handler function for |
| * each recognized option. Once all options have been parsed, a separate non-option handler |
| * function (`nonOptArgHandler`) is called once to process any remaining arguments. |
| * |
| * |
| * ## OPTION SETS |
| * |
| * An OptionSet contains a set of option definitions along with a pointer to a handler function |
| * that will be called when one of the associated options is encountered. Option sets also |
| * contain help text describing the syntax and purpose of each option (see OPTION HELP below). |
| * Option sets are designed to allow the creation of re-usable collections of related options. |
| * This simplifies the effort needed to maintain multiple applications that accept similar options |
| * (e.g. test applications). |
| * |
| * There are two patterns for defining OptionSets--one can either initialize an instance of the |
| * OptionSet struct itself, e.g. as a static global, or subclass OptionSetBase and provide a |
| * constructor. The latter uses a pure virtual `HandleOption()` function to delegate option |
| * handling to the subclass. |
| * |
| * Lists of OptionSets are passed to the ParseArgs() function as a NULL-terminated array of pointers. |
| * E.g.: |
| * |
| * static OptionSet gToolOptions = |
| * { |
| * HandleOption, // handler function |
| * gToolOptionDefs, // array of option definitions |
| * "GENERAL OPTIONS", // help group |
| * gToolOptionHelp // option help text |
| * }; |
| * |
| * static OptionSet *gOptionSets[] = |
| * { |
| * &gToolOptions, |
| * &gNetworkOptions, |
| * &gTestingOptions, |
| * &gHelpOptions, |
| * NULL |
| * }; |
| * |
| * int main(int argc, char *argv[]) |
| * { |
| * if (!ParseArgs("test-app", argc, argv, gOptionSets)) |
| * { |
| * ... |
| * } |
| * } |
| * |
| * |
| * ## OPTION DEFINITIONS |
| * |
| * Options are defined using the `OptionDef` structure. Option definitions are organized as an array |
| * of OptionDef elements, where each element contains: the name of the option, a integer id that is |
| * used to identify the option, and whether the option expects/allows an argument. The end of the |
| * option array is signaled by a NULL Name field. E.g.: |
| * |
| * enum |
| * { |
| * kOpt_Listen = 1000, |
| * kOpt_Length, |
| * kOpt_Count, |
| * }; |
| * |
| * static OptionDef gToolOptionDefs[] = |
| * { |
| * // NAME REQUIRES/ALLOWS ARG? ID/SHORT OPTION CHAR |
| * // ============================================================ |
| * { "listen", kNoArgument, kOpt_Listen }, |
| * { "length", kArgumentRequired, kOpt_Length }, |
| * { "count", kArgumentRequired, kOpt_Count }, |
| * { "num", kArgumentRequired, kOpt_Count }, // alias for --count |
| * { "debug", kArgumentOptional, 'd' }, |
| * { "help", kNoArgument, 'h' }, |
| * { NULL } |
| * }; |
| * |
| * |
| * ## OPTION IDS |
| * |
| * Option ids identify options to the code that handles them (the OptionHandler function). Option ids |
| * are relative to the OptionSet in which they appear, and thus may be reused across different |
| * OptionSets (however see SHORT OPTIONS below). Common convention is to start numbering option ids |
| * at 1000, however any number > 128 can be used. Alias options can be created by using the same |
| * option id with different option names. |
| * |
| * |
| * ## SHORT OPTIONS |
| * |
| * Unlike getopt_long(3), ParseArgs() does not take a separate string specifying the list of short |
| * option characters. Rather, any option whose id value falls in the range of graphical ASCII |
| * characters will allow that character to be used as a short option. |
| * |
| * ParseArgs() requires that short option characters be unique across *all* OptionSets. Because of |
| * this, the use of short options is discouraged for any OptionSets that are shared across programs |
| * due to the significant chance for collisions. Short options characters may be reused within a |
| * single OptionSet to allow for the creation of alias long option names. |
| * |
| * |
| * ## OPTION HELP |
| * |
| * Each OptionSet contains an `OptionHelp` string that describes the purpose and syntax of the |
| * associated options. These strings are used by the `PrintOptionHelp()` function to generate |
| * option usage information. |
| * |
| * By convention, option help strings consist of a syntax example following by a textual |
| * description of the option. If the option has a short version, or an alias name, it is given |
| * before primary long name. For consistency, syntax lines are indented with 2 spaces, while |
| * description lines are indented with 7 spaces. A single blank line follows each option |
| * description, including the last one. |
| * |
| * E.g.: |
| * |
| * static const char *const gToolOptionHelp = |
| * " --listen\n" |
| * " Listen and respond to requests sent from another node.\n" |
| * "\n" |
| * " --length <num>\n" |
| * " Send requests with the specified number of bytes in the payload.\n" |
| * "\n" |
| * " --num, --count <num>\n" |
| * " Send the specified number of requests and exit.\n" |
| * "\n" |
| * " -d, --debug [<level>]\n" |
| * " Set debug logging to the given level. (Default: 1)\n" |
| * "\n" |
| * " -h, --help\n" |
| * " Print help information.\n" |
| * "\n"; |
| * |
| * |
| * ## OPTION HELP GROUPS |
| * |
| * OptionSets contain a `HelpGroupName` string which is used to group options together in the |
| * help output. The `PrintOptionHelp()` function uses the HelpGroupName as a section title in |
| * the generated usage output. If multiple OptionSets have the same HelpGroupName, |
| * PrintOptionHelp() will print the option help for the different OptionSets together under |
| * a common section title. |
| * |
| */ |
| bool ParseArgs(const char * progName, int argc, char * const argv[], OptionSet * optSets[], |
| NonOptionArgHandlerFunct nonOptArgHandler, bool ignoreUnknown) |
| { |
| bool res = false; |
| char optName[64]; |
| char * optArg; |
| char * shortOpts = nullptr; |
| struct option * longOpts = nullptr; |
| OptionSet * curOptSet; |
| OptionDef * curOpt; |
| bool handlerRes; |
| #if CHIP_CONFIG_NON_POSIX_LONG_OPT |
| int lastOptIndex = 0; |
| int subOptIndex = 0; |
| int currentOptIndex = 0; |
| #endif // CHIP_CONFIG_NON_POSIX_LONG_OPT |
| |
| // The getopt() functions do not support recursion, so exit immediately with an |
| // error if called recursively. |
| if (gActiveOptionSets != nullptr) |
| { |
| PrintArgError("INTERNAL ERROR: ParseArgs() called recursively\n", progName); |
| return false; |
| } |
| |
| // The C standard mandates that argv[argc] == NULL and certain versions of getopt() require this |
| // to function properly. So fail if this is not true. |
| if (argv[argc] != nullptr) |
| { |
| PrintArgError("INTERNAL ERROR: argv[argc] != NULL\n", progName); |
| return false; |
| } |
| |
| // Set gActiveOptionSets to the current option set list. |
| gActiveOptionSets = optSets; |
| |
| #if CHIP_CONFIG_ENABLE_ARG_PARSER_VALIDITY_CHECKS |
| if (!SanityCheckOptions(optSets)) |
| goto done; |
| #endif |
| |
| // Generate a short options string in the format expected by getopt_long(). |
| shortOpts = MakeShortOptions(optSets); |
| if (shortOpts == nullptr) |
| { |
| PrintArgError("%s: Memory allocation failure\n", progName); |
| goto done; |
| } |
| |
| // Generate a list of long option structures in the format expected by getopt_long(). |
| longOpts = MakeLongOptions(optSets); |
| if (longOpts == nullptr) |
| { |
| PrintArgError("%s: Memory allocation failure\n", progName); |
| goto done; |
| } |
| |
| // Force getopt() to reset its internal state. |
| optind = 0; |
| |
| // Process any option arguments... |
| while (true) |
| { |
| int id; |
| int optIndex = -1; |
| |
| // Attempt to match the current option argument (argv[optind]) against the defined long and short options. |
| optarg = nullptr; |
| optopt = 0; |
| #if CHIP_CONFIG_NON_POSIX_LONG_OPT |
| // to check if index has changed |
| lastOptIndex = currentOptIndex; |
| // optind will not increment on error, this is why we need to keep track of the current option |
| // this is for use when getopt_long fails to find the option and we need to print the error |
| currentOptIndex = optind; |
| // if it's the first run, optind is not set and we need to find the first option ourselves |
| if (!currentOptIndex) |
| { |
| while (currentOptIndex < argc) |
| { |
| currentOptIndex++; |
| if (*argv[currentOptIndex] == '-') |
| { |
| break; |
| } |
| } |
| } |
| // similarly we need to keep track of short opts index for groups like "-fba" |
| // if the index has not changed that means we are still analysing the same group |
| if (lastOptIndex != currentOptIndex) |
| { |
| subOptIndex = 0; |
| } |
| else |
| { |
| subOptIndex++; |
| } |
| #endif // CHIP_CONFIG_NON_POSIX_LONG_OPT |
| id = getopt_long(argc, argv, shortOpts, longOpts, &optIndex); |
| |
| // Stop if there are no more options. |
| if (id == -1) |
| break; |
| |
| // If the current option is unrecognized, fail with an error message unless ignoreUnknown == true. |
| if (id == '?') |
| { |
| if (ignoreUnknown) |
| continue; |
| #if CHIP_CONFIG_NON_POSIX_LONG_OPT |
| // getopt_long doesn't tell us if the option which failed to match is long or short so check |
| bool isLongOption = false; |
| if (strlen(argv[currentOptIndex]) > 2 && argv[currentOptIndex][1] == '-') |
| { |
| isLongOption = true; |
| } |
| if (optopt == 0 || isLongOption) |
| { |
| // getopt_long function incorrectly treats unknown long option as short opt group |
| if (subOptIndex == 0) |
| { |
| PrintArgError("%s: Unknown option: %s\n", progName, argv[currentOptIndex]); |
| } |
| } |
| else if (optopt == '?') |
| { |
| PrintArgError("%s: Unknown option: -%c\n", progName, argv[currentOptIndex][subOptIndex + 1]); |
| } |
| else |
| { |
| PrintArgError("%s: Unknown option: -%c\n", progName, optopt); |
| } |
| #else |
| if (optopt != 0) |
| PrintArgError("%s: Unknown option: -%c\n", progName, optopt); |
| else |
| PrintArgError("%s: Unknown option: %s\n", progName, argv[optind - 1]); |
| #endif // CHIP_CONFIG_NON_POSIX_LONG_OPT |
| goto done; |
| } |
| |
| // If the option was recognized, but it is lacking an argument, fail with |
| // an error message. |
| if (id == ':') |
| { |
| // NOTE: with the way getopt_long() works, it is impossible to tell whether the option that |
| // was missing an argument was a long option or a short option. |
| #if CHIP_CONFIG_NON_POSIX_LONG_OPT |
| PrintArgError("%s: Missing argument for %s option\n", progName, argv[currentOptIndex]); |
| #else |
| PrintArgError("%s: Missing argument for %s option\n", progName, argv[optind - 1]); |
| #endif // CHIP_CONFIG_NON_POSIX_LONG_OPT |
| goto done; |
| } |
| |
| // If a long option was matched... |
| if (optIndex != -1) |
| { |
| |
| // Locate the option set and definition using the index value returned by getopt_long(). |
| FindOptionByIndex(optSets, optIndex, curOptSet, curOpt); |
| |
| // Form a string containing the name of the option as it appears on the command line. |
| snprintf(optName, sizeof(optName), "--%s", curOpt->Name); |
| } |
| |
| // Otherwise a short option was matched... |
| else |
| { |
| // Locate the option set and definition using the option id. |
| FindOptionById(optSets, id, curOptSet, curOpt); |
| |
| // Form a string containing the name of the short option as it would appears on the |
| // command line if given by itself. |
| snprintf(optName, sizeof(optName), "-%c", id); |
| } |
| |
| // Prevent handlers from inadvertently using the getopt global optarg. |
| optArg = optarg; |
| optarg = nullptr; |
| |
| // Call the option handler function defined for the matching option set. |
| // Exit immediately if the option handler failed. |
| handlerRes = curOptSet->OptionHandler(progName, curOptSet, id, optName, optArg); |
| if (!handlerRes) |
| goto done; |
| } |
| |
| // If supplied, call the non-option argument handler with the remaining arguments (if any). |
| if (nonOptArgHandler != nullptr) |
| { |
| if (!nonOptArgHandler(progName, argc - optind, argv + optind)) |
| goto done; |
| } |
| |
| // otherwise, if there are additional arguments, fail with an error. |
| else if (optind < argc) |
| { |
| PrintArgError("%s: Unexpected argument: %s\n", progName, argv[optind]); |
| goto done; |
| } |
| |
| res = true; |
| |
| done: |
| |
| if (shortOpts != nullptr) |
| chip::Platform::MemoryFree(shortOpts); |
| if (longOpts != nullptr) |
| chip::Platform::MemoryFree(longOpts); |
| |
| gActiveOptionSets = nullptr; |
| |
| return res; |
| } |
| |
| bool ParseArgs(const char * progName, int argc, char * const argv[], OptionSet * optSets[], |
| NonOptionArgHandlerFunct nonOptArgHandler) |
| { |
| return ParseArgs(progName, argc, argv, optSets, nonOptArgHandler, false); |
| } |
| |
| bool ParseArgs(const char * progName, int argc, char * const argv[], OptionSet * optSets[]) |
| { |
| return ParseArgs(progName, argc, argv, optSets, nullptr, false); |
| } |
| |
| /** |
| * @brief |
| * Parse a set of arguments from a given string. |
| * |
| * @param[in] progName The name of the program or context in which the arguments are |
| * being parsed. This string will be used to prefix error |
| * messages and warnings. |
| * @param[in] argStr A string containing options and arguments to be parsed. |
| * @param[in] optSets A list of pointers to `OptionSet` structures that define the legal |
| * options. The supplied list must be terminated with a NULL. |
| * @param[in] nonOptArgHandler A pointer to a function that will be called once option parsing |
| * is complete with any remaining non-option arguments . The function |
| * is called regardless of whether any arguments remain. If a NULL |
| * is passed `ParseArgs()` will report an error if any non-option |
| * arguments are present. |
| * @param[in] ignoreUnknown If true, silently ignore any unrecognized options. |
| * |
| * @return `true` if all options and non-option arguments were parsed |
| * successfully; `false` if an option was unrecognized, if one of |
| * the handler functions failed (i.e. returned false) or if an |
| * internal error occurred. |
| * |
| * @details |
| * ParseArgsFromString() splits a given string (`argStr`) into a set of arguments and parses the |
| * arguments using the ParseArgs() function. |
| * |
| * The syntax of the input strings is similar to unix shell command syntax, but with a simplified |
| * quoting scheme. Specifically: |
| * |
| * - Arguments are delimited by whitespace, unless the whitespace is quoted or escaped. |
| * |
| * - A backslash escapes the following character, causing it to be treated as a normal character. |
| * The backslash itself is stripped. |
| * |
| * - Single or double quotes begin/end quoted substrings. Within a substring, the only special |
| * characters are backslash, which escapes the next character, and the corresponding end quote. |
| * The begin/end quote characters are stripped. |
| * |
| * E.g.: |
| * |
| * --listen --count 10 --sw-version '1.0 (DEVELOPMENT)' "--hostname=nest.com" |
| * |
| */ |
| bool ParseArgsFromString(const char * progName, const char * argStr, OptionSet * optSets[], |
| NonOptionArgHandlerFunct nonOptArgHandler, bool ignoreUnknown) |
| { |
| char ** argv = nullptr; |
| int argc; |
| bool res; |
| |
| chip::Platform::ScopedMemoryString argStrCopy(argStr, strlen(argStr)); |
| if (!argStrCopy) |
| { |
| PrintArgError("%s: Memory allocation failure\n", progName); |
| return false; |
| } |
| |
| argc = SplitArgs(argStrCopy.Get(), argv, const_cast<char *>(progName)); |
| if (argc < 0) |
| { |
| PrintArgError("%s: Memory allocation failure\n", progName); |
| return false; |
| } |
| |
| res = ParseArgs(progName, argc, argv, optSets, nonOptArgHandler, ignoreUnknown); |
| |
| chip::Platform::MemoryFree(argv); |
| |
| return res; |
| } |
| |
| bool ParseArgsFromString(const char * progName, const char * argStr, OptionSet * optSets[], |
| NonOptionArgHandlerFunct nonOptArgHandler) |
| { |
| return ParseArgsFromString(progName, argStr, optSets, nonOptArgHandler, false); |
| } |
| |
| bool ParseArgsFromString(const char * progName, const char * argStr, OptionSet * optSets[]) |
| { |
| return ParseArgsFromString(progName, argStr, optSets, nullptr, false); |
| } |
| |
| /** |
| * @brief |
| * Parse a set of arguments from a named environment variable |
| * |
| * @param[in] progName The name of the program or context in which the arguments are |
| * being parsed. This string will be used to prefix error |
| * messages and warnings. |
| * @param[in] varName The name of the environment variable. |
| * @param[in] optSets A list of pointers to `OptionSet` structures that define the legal |
| * options. The supplied list must be terminated with a NULL. |
| * @param[in] nonOptArgHandler A pointer to a function that will be called once option parsing |
| * is complete with any remaining non-option arguments . The function |
| * is called regardless of whether any arguments remain. If a NULL |
| * is passed `ParseArgs()` will report an error if any non-option |
| * arguments are present. |
| * @param[in] ignoreUnknown If true, silently ignore any unrecognized options. |
| * |
| * @return `true` if all options and non-option arguments were parsed |
| * successfully, or if the specified environment variable is not set; |
| * `false` if an option was unrecognized, if one of the handler |
| * functions failed (i.e. returned false) or if an internal error |
| * occurred. |
| * |
| * @details |
| * ParseArgsFromEnvVar() reads a named environment variable and passes the value to `ParseArgsFromString()` |
| * for parsing. If the environment variable is not set, the function does nothing. |
| */ |
| |
| bool ParseArgsFromEnvVar(const char * progName, const char * varName, OptionSet * optSets[], |
| NonOptionArgHandlerFunct nonOptArgHandler, bool ignoreUnknown) |
| { |
| const char * argStr = getenv(varName); |
| if (argStr == nullptr) |
| return true; |
| return ParseArgsFromString(progName, argStr, optSets, nonOptArgHandler, ignoreUnknown); |
| } |
| |
| bool ParseArgsFromEnvVar(const char * progName, const char * varName, OptionSet * optSets[]) |
| { |
| return ParseArgsFromEnvVar(progName, varName, optSets, nullptr, false); |
| } |
| |
| bool ParseArgsFromEnvVar(const char * progName, const char * varName, OptionSet * optSets[], |
| NonOptionArgHandlerFunct nonOptArgHandler) |
| { |
| return ParseArgsFromEnvVar(progName, varName, optSets, nonOptArgHandler, false); |
| } |
| |
| /** |
| * @brief |
| * Print the help text for a specified list of options to a stream. |
| * |
| * @param[in] optSets A list of pointers to `OptionSet` structures that contain the |
| * help text to print. |
| * @param[in] s The FILE stream to which the help text should be printed. |
| * |
| */ |
| void PrintOptionHelp(OptionSet * optSets[], FILE * s) |
| { |
| // Get a list of the unique help group names for the given option sets. |
| const char ** helpGroupNames = MakeUniqueHelpGroupNamesList(optSets); |
| if (helpGroupNames == nullptr) |
| { |
| PrintArgError("Memory allocation failure\n"); |
| return; |
| } |
| |
| // For each help group... |
| for (size_t nameIndex = 0; helpGroupNames[nameIndex] != nullptr; nameIndex++) |
| { |
| // Print the group name. |
| PutStringWithBlankLine(s, helpGroupNames[nameIndex]); |
| |
| // Print the option help text for all options that have the same group name. |
| for (size_t optSetIndex = 0; optSets[optSetIndex] != nullptr; optSetIndex++) |
| if (strcasecmp(helpGroupNames[nameIndex], optSets[optSetIndex]->HelpGroupName) == 0) |
| { |
| PutStringWithBlankLine(s, optSets[optSetIndex]->OptionHelp); |
| } |
| } |
| |
| chip::Platform::MemoryFree(helpGroupNames); |
| } |
| |
| /** |
| * @brief |
| * Print an error message associated with argument parsing. |
| * |
| * @param[in] msg The message to be printed. |
| * |
| * @details |
| * Default function used to print error messages that arise due to the parsing |
| * of arguments. |
| * |
| * Applications should call through the PrintArgError function pointer, rather |
| * than calling this function directly. |
| */ |
| void ENFORCE_FORMAT(1, 2) DefaultPrintArgError(const char * msg, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, msg); |
| vfprintf(stderr, msg, ap); |
| va_end(ap); |
| } |
| |
| /** |
| * Parse a string as a boolean value. |
| * |
| * This function accepts the following input values (case-insensitive): |
| * "true", "yes", "t", "y", "1", "false", "no", "f", "n", "0". |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the value to parse. |
| * @param[out] output A reference to storage for a bool to which the parsed |
| * value will be stored on success. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseBoolean(const char * str, bool & output) |
| { |
| if (strcasecmp(str, "true") == 0 || strcasecmp(str, "yes") == 0 || |
| ((str[0] == '1' || str[0] == 't' || str[0] == 'T' || str[0] == 'y' || str[0] == 'Y') && str[1] == 0)) |
| { |
| output = true; |
| return true; |
| } |
| |
| if (strcasecmp(str, "false") == 0 || strcasecmp(str, "no") == 0 || |
| ((str[0] == '0' || str[0] == 'f' || str[0] == 'F' || str[0] == 'n' || str[0] == 'N') && str[1] == 0)) |
| { |
| output = false; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Parse and attempt to convert a string to a 64-bit unsigned integer, |
| * applying the appropriate interpretation based on the base parameter. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the integer to parse. |
| * @param[out] output A reference to storage for a 64-bit unsigned integer |
| * to which the parsed value will be stored on success. |
| * @param[in] base The base according to which the string should be |
| * interpreted and parsed. If 0 or 16, the string may |
| * be hexadecimal and prefixed with "0x". Otherwise, a 0 |
| * is implied as 10 unless a leading 0 is encountered in |
| * which 8 is implied. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseInt(const char * str, uint64_t & output, int base) |
| { |
| char * parseEnd; |
| |
| errno = 0; |
| output = strtoull(str, &parseEnd, base); |
| |
| return parseEnd > str && *parseEnd == 0 && (output != ULLONG_MAX || errno == 0); |
| } |
| |
| /** |
| * Parse and attempt to convert a string to a 32-bit unsigned integer, |
| * applying the appropriate interpretation based on the base parameter. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the integer to parse. |
| * @param[out] output A reference to storage for a 32-bit unsigned integer |
| * to which the parsed value will be stored on success. |
| * @param[in] base The base according to which the string should be |
| * interpreted and parsed. If 0 or 16, the string may |
| * be hexadecimal and prefixed with "0x". Otherwise, a 0 |
| * is implied as 10 unless a leading 0 is encountered in |
| * which 8 is implied. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseInt(const char * str, uint32_t & output, int base) |
| { |
| char * parseEnd; |
| unsigned long v; |
| |
| errno = 0; |
| v = strtoul(str, &parseEnd, base); |
| if (!CanCastTo<uint32_t>(v)) |
| { |
| return false; |
| } |
| output = static_cast<uint32_t>(v); |
| |
| return parseEnd > str && *parseEnd == 0 && (v != ULONG_MAX || errno == 0); |
| } |
| |
| /** |
| * Parse and attempt to convert a string to a 32-bit signed integer, |
| * applying the appropriate interpretation based on the base parameter. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the integer to parse. |
| * @param[out] output A reference to storage for a 32-bit signed integer |
| * to which the parsed value will be stored on success. |
| * @param[in] base The base according to which the string should be |
| * interpreted and parsed. If 0 or 16, the string may |
| * be hexadecimal and prefixed with "0x". Otherwise, a 0 |
| * is implied as 10 unless a leading 0 is encountered in |
| * which 8 is implied. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseInt(const char * str, int32_t & output, int base) |
| { |
| char * parseEnd; |
| long v; |
| |
| errno = 0; |
| v = strtol(str, &parseEnd, base); |
| if (!CanCastTo<int32_t>(v)) |
| { |
| return false; |
| } |
| output = static_cast<int32_t>(v); |
| |
| return parseEnd > str && *parseEnd == 0 && ((v != LONG_MIN && v != LONG_MAX) || errno == 0); |
| } |
| |
| /** |
| * Parse and attempt to convert a string to a 16-bit unsigned integer, |
| * applying the appropriate interpretation based on the base parameter. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the integer to parse. |
| * @param[out] output A reference to storage for a 16-bit unsigned integer |
| * to which the parsed value will be stored on success. |
| * @param[in] base The base according to which the string should be |
| * interpreted and parsed. If 0 or 16, the string may |
| * be hexadecimal and prefixed with "0x". Otherwise, a 0 |
| * is implied as 10 unless a leading 0 is encountered in |
| * which 8 is implied. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseInt(const char * str, uint16_t & output, int base) |
| { |
| uint32_t v; |
| |
| if (!ParseInt(str, v, base) || !CanCastTo<uint16_t>(v)) |
| { |
| return false; |
| } |
| output = static_cast<uint16_t>(v); |
| |
| return true; |
| } |
| |
| /** |
| * Parse and attempt to convert a string to a 8-bit unsigned integer, |
| * applying the appropriate interpretation based on the base parameter. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the integer to parse. |
| * @param[out] output A reference to storage for a 8-bit unsigned integer |
| * to which the parsed value will be stored on success. |
| * @param[in] base The base according to which the string should be |
| * interpreted and parsed. If 0 or 16, the string may |
| * be hexadecimal and prefixed with "0x". Otherwise, a 0 |
| * is implied as 10 unless a leading 0 is encountered in |
| * which 8 is implied. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseInt(const char * str, uint8_t & output, int base) |
| { |
| uint32_t v; |
| |
| if (!ParseInt(str, v, base) || !CanCastTo<uint8_t>(v)) |
| { |
| return false; |
| } |
| output = static_cast<uint8_t>(v); |
| |
| return true; |
| } |
| |
| /** |
| * Parse and attempt to convert a string interpreted as a decimal |
| * value to a 64-bit unsigned integer, applying the appropriate |
| * interpretation based on the base parameter. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the integer to parse. |
| * @param[out] output A reference to storage for a 64-bit unsigned integer |
| * to which the parsed value will be stored on success. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseInt(const char * str, uint64_t & output) |
| { |
| const int base = 10; |
| |
| return ParseInt(str, output, base); |
| } |
| |
| /** |
| * Parse and attempt to convert a string interpreted as a decimal |
| * value to a 32-bit unsigned integer, applying the appropriate |
| * interpretation based on the base parameter. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the integer to parse. |
| * @param[out] output A reference to storage for a 32-bit unsigned integer |
| * to which the parsed value will be stored on success. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseInt(const char * str, uint32_t & output) |
| { |
| const int base = 10; |
| |
| return ParseInt(str, output, base); |
| } |
| |
| /** |
| * Parse and attempt to convert a string interpreted as a decimal |
| * value to a 32-bit signed integer, applying the appropriate |
| * interpretation based on the base parameter. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the integer to parse. |
| * @param[out] output A reference to storage for a 32-bit signed integer |
| * to which the parsed value will be stored on success. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseInt(const char * str, int32_t & output) |
| { |
| const int base = 10; |
| |
| return ParseInt(str, output, base); |
| } |
| |
| /** |
| * Parse and attempt to convert a string interpreted as a decimal |
| * value to a 16-bit unsigned integer, applying the appropriate |
| * interpretation based on the base parameter. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the integer to parse. |
| * @param[out] output A reference to storage for a 16-bit unsigned integer |
| * to which the parsed value will be stored on success. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseInt(const char * str, uint16_t & output) |
| { |
| const int base = 10; |
| uint32_t output32 = 0; |
| |
| if ((ParseInt(str, output32, base)) && (output32 <= USHRT_MAX)) |
| { |
| output = ((1 << 16) - 1) & output32; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Parse and attempt to convert a string interpreted as a decimal |
| * value to a 16-bit signed integer, applying the appropriate |
| * interpretation based on the base parameter. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the integer to parse. |
| * @param[out] output A reference to storage for a 16-bit signed integer |
| * to which the parsed value will be stored on success. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseInt(const char * str, int16_t & output) |
| { |
| const int base = 10; |
| int32_t output32 = 0; |
| |
| if ((ParseInt(str, output32, base)) && (output32 <= SHRT_MAX && output32 >= SHRT_MIN)) |
| { |
| output = static_cast<int16_t>(output32); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Parse and attempt to convert a string interpreted as a decimal |
| * value to a 8-bit unsigned integer, applying the appropriate |
| * interpretation based on the base parameter. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string representing |
| * the integer to parse. |
| * @param[out] output A reference to storage for a 8-bit unsigned integer |
| * to which the parsed value will be stored on success. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseInt(const char * str, uint8_t & output) |
| { |
| const int base = 10; |
| uint32_t output32 = 0; |
| |
| if ((ParseInt(str, output32, base)) && (output32 <= UCHAR_MAX)) |
| { |
| output = ((1 << 8) - 1) & output32; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| #if CHIP_ARG_PARSER_PARSE_FABRIC_ID |
| /** |
| * Parse a CHIP fabric id in text form. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string containing |
| * the fabric id to parse. |
| * @param[out] output A reference to an uint64_t lvalue in which the |
| * parsed value will be stored on success. |
| * @param[in] allowReserved If true, allow the parsing of fabric ids in the |
| * reserved range. |
| * |
| * @return true if the value was successfully parsed; false if not. |
| * |
| * @details |
| * The ParseFabricId() function accepts a 64-bit fabric id given in hex format, |
| * with or without a leading '0x'. |
| */ |
| bool ParseFabricId(const char * str, uint64_t & fabricId, bool allowReserved) |
| { |
| char * parseEnd; |
| |
| errno = 0; |
| fabricId = strtoull(str, &parseEnd, 16); |
| return parseEnd > str && *parseEnd == 0 && (fabricId != ULLONG_MAX || errno == 0) && |
| (allowReserved || fabricId < kReservedFabricIdStart); |
| } |
| #endif // CHIP_ARG_PARSER_PARSE_FABRIC_ID |
| |
| /** |
| * Parse and attempt to convert a string to a 16-bit unsigned subnet |
| * ID, interpretting the string as hexadecimal. |
| * |
| * @param[in] str A pointer to a NULL-terminated C string |
| * representing the subnet ID, formatted as a |
| * hexadecimal, to parse. |
| * @param[in,out] subnetId A reference to storage for a 16-bit unsigned |
| * integer to which the parsed subnet ID value |
| * will be stored on success. |
| * |
| * @return true on success; otherwise, false on failure. |
| */ |
| bool ParseSubnetId(const char * str, uint16_t & subnetId) |
| { |
| char * parseEnd; |
| unsigned long temp; |
| bool valid; |
| |
| // Reset errno per the strtoul manual page. |
| |
| errno = 0; |
| |
| // Attempt to parse the subnet ID as a hexadecimal number. |
| |
| temp = strtoul(str, &parseEnd, 16); |
| |
| // Determine if the parse and conversion were valid. |
| |
| valid = (parseEnd > str && // Parsed some valid hexadecimal digits |
| *parseEnd == 0 && // Encountered no invalid hexadecimal digits |
| (temp != ULONG_MAX || errno == 0) && // No overflow (ERANGE) or invalid base (EINVAL) errors |
| temp <= USHRT_MAX); // Parsed value is valid for the domain (subnet ID) |
| |
| if (valid) |
| { |
| subnetId = static_cast<uint16_t>(temp); |
| } |
| |
| return valid; |
| } |
| |
| /** |
| * Parse a string of bytes given in hex form. |
| * |
| * @param[in] hexStr A pointer to the string to parse. |
| * @param[in] strLen The number of characters in hexStr to parse. |
| * @param[in] outBuf A pointer to a buffer into which the parse bytes will |
| * be stored. |
| * @param[in] outBufSize The size of the buffer pointed at by `outBuf`. |
| * @param[out] outDataLen A reference to an integer that will receive the total |
| * number of bytes parsed. In the event outBuf is not |
| * big enough to hold the given number of bytes, `outDataLen` |
| * will be set to UINT32_MAX. |
| * |
| * @return true if the value was successfully parsed; false if the input data is malformed, |
| * or if `outBuf` is too small. |
| * |
| * @details |
| * ParseHexString() expects the input to be in the form of pairs of hex digits (upper or lower case). |
| * Hex pairs can optionally be separated by any of the following characters: colon, semicolon, comma, period or dash. |
| * Additionally, whitespace characters anywhere in the input string are ignored. |
| */ |
| bool ParseHexString(const char * hexStr, uint32_t strLen, uint8_t * outBuf, uint32_t outBufSize, uint32_t & outDataLen) |
| { |
| bool isFirstNibble = true; |
| uint8_t firstNibbleVal = 0; |
| const char * p = hexStr; |
| uint32_t dataLen = 0; |
| |
| outDataLen = 0; |
| |
| for (; strLen > 0; p++, strLen--) |
| { |
| char c = *p; |
| uint8_t nibbleVal; |
| |
| if (c == 0) |
| break; |
| if (c >= '0' && c <= '9') |
| nibbleVal = static_cast<uint8_t>(c - '0'); |
| else if (c >= 'a' && c <= 'f') |
| nibbleVal = static_cast<uint8_t>(10 + (c - 'a')); |
| else if (c >= 'A' && c <= 'F') |
| nibbleVal = static_cast<uint8_t>(10 + (c - 'A')); |
| else if (isspace(c)) |
| continue; |
| else if (isFirstNibble && (c == ':' || c == ';' || c == ',' || c == '.' || c == '-')) |
| continue; |
| else |
| { |
| outDataLen = static_cast<decltype(strLen)>(p - hexStr); |
| return false; |
| } |
| |
| if (isFirstNibble) |
| { |
| firstNibbleVal = nibbleVal; |
| isFirstNibble = false; |
| } |
| else |
| { |
| if (outBufSize == 0) |
| { |
| outDataLen = UINT32_MAX; |
| return false; |
| } |
| |
| *outBuf = static_cast<uint8_t>(firstNibbleVal << 4 | nibbleVal); |
| |
| outBuf++; |
| outBufSize--; |
| dataLen++; |
| |
| isFirstNibble = true; |
| } |
| } |
| |
| if (!isFirstNibble) |
| { |
| outDataLen = static_cast<decltype(strLen)>(p - hexStr); |
| return false; |
| } |
| |
| outDataLen = dataLen; |
| |
| return true; |
| } |
| |
| // ===== HelpOptions Methods ===== |
| |
| HelpOptions::HelpOptions(const char * appName, const char * appUsage, const char * appVersion) : |
| HelpOptions(appName, appUsage, appVersion, nullptr) |
| {} |
| |
| HelpOptions::HelpOptions(const char * appName, const char * appUsage, const char * appVersion, const char * appDesc) |
| { |
| // clang-format off |
| static OptionDef optionDefs[] = |
| { |
| { "help", kNoArgument, 'h' }, |
| { "version", kNoArgument, 'v' }, |
| { } |
| }; |
| // clang-format on |
| OptionDefs = optionDefs; |
| |
| HelpGroupName = "HELP OPTIONS"; |
| |
| OptionHelp = " -h, --help\n" |
| " Print this output and then exit.\n" |
| "\n" |
| " -v, --version\n" |
| " Print the version and then exit.\n" |
| "\n"; |
| |
| AppName = appName; |
| AppUsage = appUsage; |
| AppVersion = appVersion; |
| AppDesc = appDesc; |
| } |
| |
| /** |
| * Print a short description of the command's usage followed by instructions on how to get more help. |
| */ |
| void HelpOptions::PrintBriefUsage(FILE * s) const |
| { |
| PutStringWithNewLine(s, AppUsage); |
| fprintf(s, "Try `%s --help' for more information.\n", AppName); |
| } |
| |
| /** |
| * Print the full usage information, including information on all available options. |
| */ |
| void HelpOptions::PrintLongUsage(OptionSet ** optSets, FILE * s) const |
| { |
| PutStringWithBlankLine(s, AppUsage); |
| if (AppDesc != nullptr) |
| { |
| PutStringWithBlankLine(s, AppDesc); |
| } |
| PrintOptionHelp(optSets, s); |
| } |
| |
| void HelpOptions::PrintVersion(FILE * s) const |
| { |
| fprintf(s, "%s ", AppName); |
| PutStringWithNewLine(s, (AppVersion != nullptr) ? AppVersion : "(unknown version)"); |
| } |
| |
| bool HelpOptions::HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) |
| { |
| switch (id) |
| { |
| case 'h': |
| PrintLongUsage(gActiveOptionSets, stdout); |
| exit(EXIT_SUCCESS); |
| break; |
| case 'v': |
| PrintVersion(stdout); |
| exit(EXIT_SUCCESS); |
| break; |
| default: |
| PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", progName, name); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // ===== Private/Internal Methods ===== |
| |
| OptionSetBase::OptionSetBase() |
| { |
| OptionHandler = CallHandleFunct; |
| } |
| |
| bool OptionSetBase::CallHandleFunct(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) |
| { |
| return static_cast<OptionSetBase *>(optSet)->HandleOption(progName, optSet, id, name, arg); |
| } |
| |
| static char * MakeShortOptions(OptionSet ** optSets) |
| { |
| size_t i = 0; |
| |
| // Count the number of options. |
| size_t totalOptions = CountAllOptions(optSets); |
| |
| // Allocate a block of memory big enough to hold the maximum possible size short option string. |
| // The buffer needs to be big enough to hold up to 3 characters per short option plus an initial |
| // ":" and a terminating null. |
| size_t arraySize = 2 + (totalOptions * 3); |
| char * shortOpts = static_cast<char *>(chip::Platform::MemoryAlloc(arraySize)); |
| if (shortOpts == nullptr) |
| return nullptr; |
| |
| // Prefix the string with ':'. This tells getopt() to signal missing option arguments distinct |
| // from unknown options. |
| shortOpts[i++] = ':'; |
| |
| // For each option set... |
| for (; *optSets != nullptr; optSets++) |
| { |
| // For each option in the current option set... |
| for (OptionDef * optDef = (*optSets)->OptionDefs; optDef->Name != nullptr; optDef++) |
| { |
| // If the option id (val) is suitable as a short option character, add it to the short |
| // option string. Append ":" if the option requires an argument and "::" if the argument |
| // is optional. |
| if (IsShortOptionChar(optDef->Id)) |
| { |
| shortOpts[i++] = static_cast<char>(optDef->Id); |
| if (optDef->ArgType != kNoArgument) |
| shortOpts[i++] = ':'; |
| if (optDef->ArgType == kArgumentOptional) |
| shortOpts[i++] = ':'; |
| } |
| } |
| } |
| |
| // Terminate the short options string. |
| shortOpts[i++] = 0; |
| |
| return shortOpts; |
| } |
| |
| static struct option * MakeLongOptions(OptionSet ** optSets) |
| { |
| size_t totalOptions = CountAllOptions(optSets); |
| |
| // Allocate an array to hold the list of long options. |
| size_t arraySize = (sizeof(struct option) * (totalOptions + 1)); |
| struct option * longOpts = static_cast<struct option *>(chip::Platform::MemoryAlloc(arraySize)); |
| if (longOpts == nullptr) |
| return nullptr; |
| |
| // For each option set... |
| size_t i = 0; |
| for (; *optSets != nullptr; optSets++) |
| { |
| // Copy the option definitions into the long options array. |
| for (OptionDef * optDef = (*optSets)->OptionDefs; optDef->Name != nullptr; optDef++) |
| { |
| longOpts[i].name = optDef->Name; |
| longOpts[i].has_arg = static_cast<int>(optDef->ArgType); |
| longOpts[i].flag = nullptr; |
| longOpts[i].val = optDef->Id; |
| i++; |
| } |
| } |
| |
| // Terminate the long options array. |
| longOpts[i].name = nullptr; |
| |
| return longOpts; |
| } |
| |
| static int32_t SplitArgs(char * argStr, char **& argList, char * initialArg) |
| { |
| enum |
| { |
| InitialArgListSize = 10 |
| }; |
| size_t argListSize = 0; |
| int32_t argCount = 0; |
| |
| // Allocate an array to hold pointers to the arguments. |
| argList = static_cast<char **>(chip::Platform::MemoryAlloc(sizeof(char *) * InitialArgListSize)); |
| if (argList == nullptr) |
| return -1; |
| argListSize = InitialArgListSize; |
| |
| // If an initial argument was supplied, make it the first argument in the array. |
| if (initialArg != nullptr) |
| { |
| argList[0] = initialArg; |
| argCount = 1; |
| } |
| |
| // Parse arguments from the input string until it is exhausted. |
| while (true) |
| { |
| char * nextArg = argStr; |
| |
| // Get the argument in the input string. Note that this modifies the string buffer. |
| if (!GetNextArg(argStr)) |
| break; |
| |
| // Grow the arg list array if needed. Note that we reserve one slot at the end of the array |
| // for a NULL entry. |
| if (argListSize == static_cast<size_t>(argCount + 1)) |
| { |
| argListSize *= 2; |
| argList = static_cast<char **>(chip::Platform::MemoryRealloc(argList, argListSize)); |
| if (argList == nullptr) |
| return -1; |
| } |
| |
| // Append the argument. |
| argList[argCount++] = nextArg; |
| } |
| |
| // Set the last element in the array to NULL, but do not include this in the count of elements. |
| // This is mandated by the C standard and some versions of getopt_long() depend on it. |
| argList[argCount] = nullptr; |
| |
| return argCount; |
| } |
| |
| static bool GetNextArg(char *& parsePoint) |
| { |
| char quoteChar = 0; |
| char * argEnd = parsePoint; |
| |
| // Skip any leading whitespace. |
| while (*parsePoint != 0 && isspace(*parsePoint)) |
| parsePoint++; |
| |
| // Return false if there are no further arguments. |
| if (*parsePoint == 0) |
| return false; |
| |
| // Iterate over characters until we find the end of an argument. |
| // As we iterate, we will accumulate the unquoted and unescaped |
| // argument characters in the input buffer starting at the initial |
| // parsePoint position. |
| while (*parsePoint != 0) |
| { |
| // If the current character is a backslash that is not at the end of |
| // the string, skip the backslash but copy the following character |
| // verbatim into the argument string. |
| if (*parsePoint == '\\' && *(parsePoint + 1) != 0) |
| { |
| parsePoint++; |
| } |
| |
| // Otherwise, if not within a quoted substring... |
| else if (quoteChar == 0) |
| { |
| // Whitespace marks the end of the argument. |
| if (isspace(*parsePoint)) |
| { |
| parsePoint++; |
| break; |
| } |
| |
| // If the character is a quote character, enter quoted substring mode. |
| if (*parsePoint == '"' || *parsePoint == '\'') |
| { |
| quoteChar = *parsePoint++; |
| continue; |
| } |
| } |
| |
| // Otherwise, the parse point is within a quoted substring, so... |
| else |
| { |
| // A corresponding quote character marks the end of the quoted string. |
| if (*parsePoint == quoteChar) |
| { |
| quoteChar = 0; |
| parsePoint++; |
| continue; |
| } |
| } |
| |
| // Copy the current character to the end of the argument string. |
| *argEnd++ = *parsePoint++; |
| } |
| |
| // Terminate the argument string. |
| *argEnd = 0; |
| |
| return true; |
| } |
| |
| static size_t CountOptionSets(OptionSet ** optSets) |
| { |
| size_t count = 0; |
| for (; *optSets != nullptr; optSets++) |
| count++; |
| return count; |
| } |
| |
| static size_t CountAllOptions(OptionSet ** optSets) |
| { |
| size_t count = 0; |
| for (; *optSets != nullptr; optSets++) |
| for (OptionDef * optDef = (*optSets)->OptionDefs; optDef->Name != nullptr; optDef++) |
| count++; |
| return count; |
| } |
| |
| static void FindOptionByIndex(OptionSet ** optSets, int optIndex, OptionSet *& optSet, OptionDef *& optDef) |
| { |
| for (optSet = *optSets; optSet != nullptr; optSet = *++optSets) |
| for (optDef = (*optSets)->OptionDefs; optDef->Name != nullptr; optDef++) |
| if (optIndex-- == 0) |
| return; |
| optSet = nullptr; |
| optDef = nullptr; |
| } |
| |
| static void FindOptionById(OptionSet ** optSets, int optId, OptionSet *& optSet, OptionDef *& optDef) |
| { |
| for (optSet = *optSets; optSet != nullptr; optSet = *++optSets) |
| for (optDef = (*optSets)->OptionDefs; optDef->Name != nullptr; optDef++) |
| if (optDef->Id == optId) |
| return; |
| optSet = nullptr; |
| optDef = nullptr; |
| } |
| |
| static const char ** MakeUniqueHelpGroupNamesList(OptionSet * optSets[]) |
| { |
| size_t numOptSets = CountOptionSets(optSets); |
| size_t numGroups = 0; |
| |
| const char ** groupNames = static_cast<const char **>(chip::Platform::MemoryAlloc(sizeof(const char *) * (numOptSets + 1))); |
| if (groupNames == nullptr) |
| return nullptr; |
| |
| for (size_t optSetIndex = 0; optSetIndex < numOptSets; optSetIndex++) |
| { |
| if (optSets[optSetIndex] != nullptr && optSets[optSetIndex]->OptionDefs[0].Name != nullptr) |
| { |
| for (size_t i = 0; i < numGroups; i++) |
| if (strcasecmp(groupNames[i], optSets[optSetIndex]->HelpGroupName) == 0) |
| goto skipDup; |
| groupNames[numGroups++] = optSets[optSetIndex]->HelpGroupName; |
| skipDup:; |
| } |
| } |
| |
| groupNames[numGroups] = nullptr; |
| |
| return groupNames; |
| } |
| |
| static void PutStringWithNewLine(FILE * s, const char * str) |
| { |
| size_t strLen = strlen(str); |
| fputs(str, s); |
| if (strLen == 0 || str[strLen - 1] != '\n') |
| fputs("\n", s); |
| } |
| |
| static void PutStringWithBlankLine(FILE * s, const char * str) |
| { |
| size_t strLen = strlen(str); |
| fputs(str, s); |
| if (strLen < 1 || str[strLen - 1] != '\n') |
| fputs("\n", s); |
| if (strLen < 2 || str[strLen - 2] != '\n') |
| fputs("\n", s); |
| } |
| |
| #if CHIP_CONFIG_ENABLE_ARG_PARSER_VALIDITY_CHECKS |
| |
| static bool SanityCheckOptions(OptionSet * optSets[]) |
| { |
| bool res = true; |
| |
| // Verify OptionHandler pointer |
| for (OptionSet ** optSetP = optSets; *optSetP != nullptr; optSetP++) |
| { |
| if ((*optSetP)->OptionHandler == nullptr) |
| { |
| PrintArgError("INTERNAL ERROR: Null OptionHandler in OptionSet (%s)\n", (*optSetP)->HelpGroupName); |
| res = false; |
| } |
| } |
| |
| // Verify that no two option sets use the same short option character. |
| // (Re-use of the same short option character is allowed within a single option set |
| // to allow for aliasing of long options). |
| for (OptionSet ** optSetP = optSets; *optSetP != nullptr; optSetP++) |
| for (OptionDef * optionDef = (*optSetP)->OptionDefs; optionDef->Name != nullptr; optionDef++) |
| if (IsShortOptionChar(optionDef->Id)) |
| { |
| for (OptionSet ** optSetP2 = optSets; *optSetP2 != nullptr; optSetP2++) |
| if (optSetP2 != optSetP) |
| { |
| for (OptionDef * optionDef2 = (*optSetP2)->OptionDefs; optionDef2->Name != nullptr; optionDef2++) |
| if (optionDef->Id == optionDef2->Id) |
| { |
| PrintArgError("INTERNAL ERROR: Multiple command line options configured to use " |
| "the same short option character (-%c): --%s, --%s\n", |
| optionDef->Id, optionDef->Name, optionDef2->Name); |
| res = false; |
| } |
| } |
| } |
| |
| return res; |
| } |
| |
| #endif // CHIP_CONFIG_ENABLE_ARG_PARSER_VALIDITY_CHECKS |
| |
| } // namespace ArgParser |
| } // namespace chip |
| |
| #endif // CHIP_CONFIG_ENABLE_ARG_PARSER |