blob: 93864b6114e237a5e93085199d9161212859c892 [file] [log] [blame]
/*
*
* 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 <stdint.h>
#include <inttypes.h>
#include <limits.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include "nlargparser.hpp"
#if WEAVE_CONFIG_ENABLE_ARG_PARSER
#include <getopt.h>
namespace nl {
namespace ArgParser {
using namespace nl::Inet;
using namespace nl::Weave;
static char *MakeShortOptions(OptionSet **optSets);
static struct option *MakeLongOptions(OptionSet **optSets);
static int32_t SplitArgs(char *argStr, char **& argList, char *initialArg = NULL);
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 WEAVE_CONFIG_ENABLE_ARG_PARSER_SANTIY_CHECK
static bool SanityCheckOptions(OptionSet *optSets[]);
static bool HelpTextContainsLongOption(const char *optName, const char *helpText);
static bool HelpTextContainsShortOption(char optChar, const char *helpText);
#endif // WEAVE_CONFIG_ENABLE_ARG_PARSER_SANTIY_CHECK
static inline bool IsShortOptionChar(int ch)
{
return isascii(ch) && 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 = NULL;
/**
* @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 *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 *argv[], OptionSet *optSets[], NonOptionArgHandlerFunct nonOptArgHandler, bool ignoreUnknown)
{
bool res = false;
char optName[64];
char *optArg;
char *shortOpts = NULL;
struct option *longOpts = NULL;
OptionSet *curOptSet;
OptionDef *curOpt;
bool handlerRes;
// The getopt() functions do not support recursion, so exit immediately with an
// error if called recursively.
if (gActiveOptionSets != NULL)
{
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] != NULL)
{
PrintArgError("INTERNAL ERROR: argv[argc] != NULL\n", progName);
return false;
}
// Set gActiveOptionSets to the current option set list.
gActiveOptionSets = optSets;
#if WEAVE_CONFIG_ENABLE_ARG_PARSER_SANTIY_CHECK
if (!SanityCheckOptions(optSets))
goto done;
#endif
// Generate a short options string in the format expected by getopt_long().
shortOpts = MakeShortOptions(optSets);
if (shortOpts == NULL)
{
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 == NULL)
{
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 = NULL;
optopt = 0;
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 (optopt != 0)
PrintArgError("%s: Unknown option: -%c\n", progName, optopt);
else
PrintArgError("%s: Unknown option: %s\n", progName, argv[optind-1]);
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.
PrintArgError("%s: Missing argument for %s option\n", progName, argv[optind - 1]);
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 = NULL;
// 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 != NULL)
{
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 != NULL)
free(shortOpts);
if (longOpts != NULL)
free(longOpts);
gActiveOptionSets = NULL;
return res;
}
bool ParseArgs(const char *progName, int argc, char *argv[], OptionSet *optSets[], NonOptionArgHandlerFunct nonOptArgHandler)
{
return ParseArgs(progName, argc, argv, optSets, nonOptArgHandler, false);
}
bool ParseArgs(const char *progName, int argc, char *argv[], OptionSet *optSets[])
{
return ParseArgs(progName, argc, argv, optSets, NULL, 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 *argStrCopy = NULL;
char **argv = NULL;
int argc;
bool res;
argStrCopy = strdup(argStr);
if (argStrCopy == NULL)
{
PrintArgError("%s: Memory allocation failure\n", progName);
return false;
}
argc = SplitArgs(argStrCopy, argv, (char *)progName);
if (argc < 0)
{
PrintArgError("%s: Memory allocation failure\n", progName);
free(argStrCopy);
return false;
}
res = ParseArgs(progName, argc, argv, optSets, nonOptArgHandler, ignoreUnknown);
free(argStrCopy);
free(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, NULL, 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 == NULL)
return true;
return ParseArgsFromString(progName, argStr, optSets, nonOptArgHandler, ignoreUnknown);
}
bool ParseArgsFromEnvVar(const char *progName, const char *varName, OptionSet *optSets[])
{
return ParseArgsFromEnvVar(progName, varName, optSets, NULL, 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 == NULL)
{
PrintArgError("Memory allocation failure\n");
return;
}
// For each help group...
for (size_t nameIndex = 0; helpGroupNames[nameIndex] != NULL; 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] != NULL; optSetIndex++)
if (strcasecmp(helpGroupNames[nameIndex], optSets[optSetIndex]->HelpGroupName) == 0)
{
PutStringWithBlankLine(s, optSets[optSetIndex]->OptionHelp);
}
}
free(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 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);
output = 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);
output = v;
return parseEnd > str && *parseEnd == 0 && ((v != LONG_MIN && v != LONG_MAX) || errno == 0);
}
/**
* 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))
{
output = ((1 << 16) - 1) & 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;
}
/**
* Parse an IP address in text form.
*
* @param[in] str A pointer to a NULL-terminated C string containing
* the address to parse.
* @param[out] output A reference to an IPAddress object in which the parsed
* value will be stored on success.
*
* @return true if the value was successfully parsed; false if not.
*/
bool ParseIPAddress(const char *str, IPAddress& output)
{
return IPAddress::FromString(str, output);
}
/**
* Parse a Weave node id in text form.
*
* @param[in] str A pointer to a NULL-terminated C string containing
* the node id to parse.
* @param[out] output A reference to an uint64_t lvalue in which the parsed
* value will be stored on success.
*
* @return true if the value was successfully parsed; false if not.
*
* @details
* The ParseNodeId() function accepts either a 64-bit node id given in hex
* format (with or without a leading '0x'), or the words 'any' or 'all' which
* are interpreted as meaning the Any node id (0xFFFFFFFFFFFFFFFF).
*/
bool ParseNodeId(const char *str, uint64_t& nodeId)
{
char *parseEnd;
if (strcasecmp(str, "any") == 0 || strcasecmp(str, "all") == 0)
{
nodeId = kAnyNodeId;
return true;
}
errno = 0;
nodeId = strtoull(str, &parseEnd, 16);
return parseEnd > str && *parseEnd == 0 && (nodeId != ULLONG_MAX || errno == 0);
}
/**
* Parse a Weave 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);
}
/**
* 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[inout] 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;
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 = c - '0';
else if (c >= 'a' && c <= 'f')
nibbleVal = 10 + (c - 'a');
else if (c >= 'A' && c <= 'F')
nibbleVal = 10 + (c - 'A');
else if (isspace(c))
continue;
else if (isFirstNibble && (c == ':' || c == ';' || c == ',' || c == '.' || c == '-'))
continue;
else
{
outDataLen = p - hexStr;
return false;
}
if (isFirstNibble)
{
firstNibbleVal = nibbleVal;
isFirstNibble = false;
}
else
{
if (outBufSize == 0)
{
outDataLen = UINT32_MAX;
return false;
}
*outBuf = firstNibbleVal << 4 | nibbleVal;
outBuf++;
outBufSize--;
dataLen++;
isFirstNibble = true;
}
}
if (!isFirstNibble)
{
outDataLen = 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, NULL)
{
}
HelpOptions::HelpOptions(const char *appName, const char *appUsage, const char *appVersion, const char *appDesc)
{
static OptionDef optionDefs[] =
{
{ "help", kNoArgument, 'h' },
{ "version", kNoArgument, 'v' },
{ NULL }
};
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)
{
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)
{
PutStringWithBlankLine(s, AppUsage);
if (AppDesc != NULL)
{
PutStringWithBlankLine(s, AppDesc);
}
PrintOptionHelp(optSets, s);
}
void HelpOptions::PrintVersion(FILE *s)
{
fprintf(s, "%s ", AppName);
PutStringWithNewLine(s, (AppVersion != NULL) ? 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 = (char *)malloc(arraySize);
if (shortOpts == NULL)
return NULL;
// Prefix the string with ':'. This tells getopt() to signal missing option arguments distinct
// from unknown options.
shortOpts[i++] = ':';
// For each option set...
for ( ; *optSets != NULL; optSets++)
{
// For each option in the current option set...
for (OptionDef *optDef = (*optSets)->OptionDefs; optDef->Name != NULL; 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++] = (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 = (struct option *)malloc(arraySize);
if (longOpts == NULL)
return NULL;
// For each option set...
size_t i = 0;
for ( ; *optSets != NULL; optSets++)
{
// Copy the option definitions into the long options array.
for (OptionDef *optDef = (*optSets)->OptionDefs; optDef->Name != NULL; optDef++)
{
longOpts[i].name = optDef->Name;
longOpts[i].has_arg = (int)optDef->ArgType;
longOpts[i].flag = NULL;
longOpts[i].val = optDef->Id;
i++;
}
}
// Terminate the long options array.
longOpts[i].name = NULL;
return longOpts;
}
static int32_t SplitArgs(char *argStr, char **& argList, char *initialArg)
{
enum { InitialArgListSize = 10 };
int32_t argListSize = 0;
int32_t argCount = 0;
// Allocate an array to hold pointers to the arguments.
argList = (char **)malloc(sizeof(char *) * InitialArgListSize);
if (argList == NULL)
return -1;
argListSize = InitialArgListSize;
// If an initial argument was supplied, make it the first argument in the array.
if (initialArg != NULL)
{
argList[0] = (char *)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 == argCount + 1)
{
argListSize *= 2;
argList = (char **)realloc(argList, argListSize);
if (argList == NULL)
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] = NULL;
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 != NULL; optSets++)
count++;
return count;
}
static size_t CountAllOptions(OptionSet **optSets)
{
size_t count = 0;
for (; *optSets != NULL; optSets++)
for (OptionDef *optDef = (*optSets)->OptionDefs; optDef->Name != NULL; optDef++)
count++;
return count;
}
static void FindOptionByIndex(OptionSet **optSets, int optIndex, OptionSet *& optSet, OptionDef *& optDef)
{
for (optSet = *optSets; optSet != NULL; optSet = *++optSets)
for (optDef = (*optSets)->OptionDefs; optDef->Name != NULL; optDef++)
if (optIndex-- == 0)
return;
optSet = NULL;
optDef = NULL;
}
static void FindOptionById(OptionSet **optSets, int optId, OptionSet *& optSet, OptionDef *& optDef)
{
for (optSet = *optSets; optSet != NULL; optSet = *++optSets)
for (optDef = (*optSets)->OptionDefs; optDef->Name != NULL; optDef++)
if (optDef->Id == optId)
return;
optSet = NULL;
optDef = NULL;
}
static const char **MakeUniqueHelpGroupNamesList(OptionSet *optSets[])
{
size_t numOptSets = CountOptionSets(optSets);
size_t numGroups = 0;
const char **groupNames = (const char **)malloc(sizeof(const char *) * (numOptSets + 1));
if (groupNames == NULL)
return NULL;
for (size_t optSetIndex = 0; optSetIndex < numOptSets; optSetIndex++)
{
if (optSets[optSetIndex] != NULL && optSets[optSetIndex]->OptionDefs[0].Name != NULL)
{
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] = NULL;
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 WEAVE_CONFIG_ENABLE_ARG_PARSER_SANTIY_CHECK
static bool SanityCheckOptions(OptionSet *optSets[])
{
bool res = true;
// Verify OptionHandler pointer
for (OptionSet **optSetP = optSets; *optSetP != NULL; optSetP++)
{
if ((*optSetP)->OptionHandler == NULL)
{
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 != NULL; optSetP++)
for (OptionDef *optionDef = (*optSetP)->OptionDefs; optionDef->Name != NULL; optionDef++)
if (IsShortOptionChar(optionDef->Id))
{
for (OptionSet **optSetP2 = optSets; *optSetP2 != NULL; optSetP2++)
if (optSetP2 != optSetP)
{
for (OptionDef *optionDef2 = (*optSetP2)->OptionDefs; optionDef2->Name != NULL; 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;
}
}
}
// Fail if the option help texts do not contain a description for each option, including both
// the option's long and short forms.
for (OptionSet **optSetP = optSets; *optSetP != NULL; optSetP++)
for (OptionDef *optionDef = (*optSetP)->OptionDefs; optionDef->Name != NULL; optionDef++)
{
if (!HelpTextContainsLongOption(optionDef->Name, (*optSetP)->OptionHelp))
{
PrintArgError("INTERNAL ERROR: No help text defined for option: --%s\n", optionDef->Name);
res = false;
}
if (IsShortOptionChar(optionDef->Id) &&
!HelpTextContainsShortOption(optionDef->Id, (*optSetP)->OptionHelp))
{
PrintArgError("INTERNAL ERROR: No help text defined for option: -%c\n", optionDef->Id);
res = false;
}
}
return res;
}
static bool HelpTextContainsLongOption(const char *optName, const char *helpText)
{
size_t nameLen = strlen(optName);
for (const char *p = helpText; (p = strstr(p, optName)) != NULL; p += nameLen)
if ((p - helpText) >= 2 && p[-1] == '-' && p[-2] == '-' &&
!isalnum(p[nameLen]) && p[nameLen] != '-')
return true;
return false;
}
static bool HelpTextContainsShortOption(char optChar, const char *helpText)
{
char optStr[3];
optStr[0] = '-';
optStr[1] = optChar;
optStr[2] = 0;
for (const char *p = helpText; (p = strstr(p, optStr)) != NULL; p += 2)
if ((p == helpText || (!isalnum(p[-1]) && p[-1] != '-')) &&
!isalnum(p[2]) && p[2] != '-')
return true;
return false;
}
#endif // WEAVE_CONFIG_ENABLE_ARG_PARSER_SANTIY_CHECK
} // namespace ArgParser
} // namespace nl
#endif // WEAVE_CONFIG_ENABLE_ARG_PARSER