Created
December 4, 2019 06:15
-
-
Save Jacob-Tate/2283ac49ce9e732a536ae1e3157b3d34 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file arclite_getopt.cpp | |
* @author Jacob I. Tate | |
* @brief Simple getopt style command line parser with short and long options | |
*/ | |
#include "arclite_getopt.hpp" | |
#include <cstdio> // vsnprintf | |
#include <cstdarg> // va_list | |
#include <cstring> | |
#if !defined(_MSC_VER) | |
#include <strings.h> // strncasecmp | |
#else | |
#include <ctype.h> // tolower | |
#endif | |
static int str_case_cmp_len(const char* s1, const char* s2, unsigned int len) | |
{ | |
#if defined(_MSC_VER) | |
for(unsigned int i = 0; i < len; i++) | |
{ | |
int c1 = tolower(s1[i]); | |
int c2 = tolower(s2[i]); | |
if(c1 < c2) return -1; | |
if(c1 > c2) return 1; | |
if(c1 == '\0' && c1 == c2) return 0; | |
} | |
return 0; | |
#else // defined(_MSC_VER) | |
return strncasecmp(s1, s2, len); | |
#endif // defined(_MSC_VER) | |
} | |
static int str_format(char* buffer, size_t buffer_size, const char* format, ...) | |
{ | |
va_list args; | |
va_start(args, format); | |
int ret = vsnprintf(buffer, buffer_size, format, args); | |
// This fixes an error lmao? | |
#if defined(_MSC_VER) | |
buffer[buffer_size - 1] = '\0'; | |
#endif | |
va_end(args); | |
return ret; | |
} | |
// | |
// Exception stuff | |
// | |
arclite_getopt_invalid_shortopt::arclite_getopt_invalid_shortopt(const std::string& exception) | |
: exception_(exception) {} | |
const char* arclite_getopt_invalid_shortopt::what() const noexcept | |
{ | |
// you could use std::string_view::data but no guarantee its null terminated | |
return std::string(exception_).c_str(); | |
} | |
arclite_getopt::arclite_getopt(int argc, const char** argv, const arclite_getopt_option_t* options) | |
: context_{ } | |
{ | |
context_.argc = (argc > 1) ? (argc - 1) : 0; // Take away command name yo! | |
context_.argv = (argc > 1) ? (argv + 1) : argv; // Take away command name | |
context_.options = options; | |
context_.current_index = 0; | |
context_.current_opt_arg = nullptr; | |
// Count the epic options | |
context_.num_options = 0; | |
const arclite_getopt_option_t* temp_options = options; | |
while(!(options->name == nullptr && options->name_short == 0)) | |
{ | |
if(options->value == '!' || options->value == '?' || | |
options->value == '+' || options->value == -1) | |
{ | |
throw arclite_getopt_invalid_shortopt( | |
std::to_string(options->value) + " is not a valid value"); | |
return; | |
} | |
context_.num_options++; | |
options++; | |
} | |
} | |
int arclite_getopt::next() | |
{ | |
// Have we processed all the commands | |
if(context_.current_index == context_.argc) | |
return -1; | |
// Reset opt-arg | |
context_.current_opt_arg = nullptr; | |
const char* current_token = context_.argv[context_.current_index]; | |
// This token have been processed | |
context_.current_index++; | |
// Check if the item is a no-option | |
if(current_token[0] && current_token[0] != '-') | |
{ | |
context_.current_opt_arg = current_token; | |
return '+'; // Return 'x' as the identifier for no option | |
} | |
const arclite_getopt_option_t* found_option = nullptr; | |
const char* found_argument = nullptr; | |
// Check for shortop | |
if(current_token[1] != '\0' && current_token[1] != '-' && current_token[2] == '\0') | |
{ | |
for(int i = 0; i < context_.num_options; i++) | |
{ | |
// Retrieve the option | |
const arclite_getopt_option_t* temp_option = context_.options + i; | |
if(temp_option->name_short == current_token[1]) | |
{ | |
found_option = temp_option; | |
// if there is a value when: | |
// current_index < argc and value | |
// in argv[current_index] dont start it with | |
// a '-' | |
if((context_.current_index != context_.argc) && | |
(context_.argv[context_.current_index][0] != '-') | |
&& | |
(temp_option->type == ARCLITE_GETOPT_TYPE_OPTIONAL || | |
temp_option->type == ARCLITE_GETOPT_TYPE_REQUIRED)) | |
{ | |
found_argument = context_.argv[context_.current_index++]; | |
} | |
break; | |
} | |
} | |
} | |
// Check for long options | |
else if(current_token[1] == '-' && current_token[2] != '\0') | |
{ | |
const char* check_option = current_token + 2; | |
for(int i = 0; i < context_.num_options; i++) | |
{ | |
// retrieve option | |
const arclite_getopt_option_t* temp_option = context_.options + i; | |
auto name_length = (unsigned int)strlen(temp_option->name); | |
if(str_case_cmp_len(temp_option->name, check_option, name_length) == 0) | |
{ | |
check_option += name_length; | |
// Find the argument if it exists | |
switch(*check_option) | |
{ | |
case '\0': | |
{ | |
// Are there more tokens that can contain | |
// the '=' case | |
if(context_.current_index < context_.argc) | |
{ | |
const char* next_token = context_.argv[context_.current_index]; | |
if(next_token[0] == '=') | |
{ | |
context_.current_index++; | |
if(next_token[1] != '\0') | |
found_argument = next_token + 1; | |
else if(context_.current_index < context_.argc) | |
found_argument = context_.argv[context_.current_index++]; | |
} | |
else if(next_token[0] != '-') | |
{ | |
context_.current_index++; | |
found_argument = next_token; | |
} | |
} | |
break; | |
} | |
case '=': | |
{ | |
if(check_option[1] != '\0') | |
{ | |
found_argument = check_option + 1; | |
} | |
else if(context_.current_index < context_.argc) | |
{ | |
found_argument = context_.argv[context_.current_index++]; | |
} | |
break; | |
} | |
default: | |
continue; // not found but matched ie --test and --testing | |
} | |
found_option = temp_option; | |
break; | |
} | |
} | |
} | |
// Malformed option '-', '-xyz' or '--' | |
else | |
{ | |
context_.current_opt_arg = current_token; | |
return '!'; | |
} | |
// No matching options | |
if(found_option == nullptr) | |
{ | |
context_.current_opt_arg = current_token; | |
return '?'; | |
} | |
if(found_argument != nullptr) | |
{ | |
switch(found_option->type) | |
{ | |
case ARCLITE_GETOPT_TYPE_FLAG_SET: | |
case ARCLITE_GETOPT_TYPE_FLAG_AND: | |
case ARCLITE_GETOPT_TYPE_FLAG_OR: | |
case ARCLITE_GETOPT_TYPE_NO_ARGS: | |
// These types should have no argument the | |
// user shouldnt have given an argument | |
context_.current_opt_arg = found_option->name; | |
return '!'; | |
case ARCLITE_GETOPT_TYPE_OPTIONAL: | |
case ARCLITE_GETOPT_TYPE_REQUIRED: | |
context_.current_opt_arg = found_argument; | |
return found_option->value; | |
} | |
} | |
// No argument was found | |
else | |
{ | |
switch(found_option->type) | |
{ | |
case ARCLITE_GETOPT_TYPE_FLAG_SET: | |
*found_option->flag = found_option->value; | |
return 0; | |
case ARCLITE_GETOPT_TYPE_FLAG_AND: | |
*found_option->flag &= found_option->value; | |
return 0; | |
case ARCLITE_GETOPT_TYPE_FLAG_OR: | |
*found_option->flag |= found_option->value; | |
return 0; | |
case ARCLITE_GETOPT_TYPE_NO_ARGS: | |
case ARCLITE_GETOPT_TYPE_OPTIONAL: | |
return found_option->value; | |
// The option retuires an argument ie --option=argument or -o arg | |
case ARCLITE_GETOPT_TYPE_REQUIRED: | |
context_.current_opt_arg = found_option->name; | |
return '!'; | |
} | |
} | |
return -1; | |
} | |
const char* arclite_getopt::current_opt_arg() | |
{ | |
return context_.current_opt_arg; | |
} | |
const char* arclite_getopt::create_help_string(char* buffer, size_t buffer_size) | |
{ | |
size_t buffer_pos = 0; | |
for(int option_index = 0; option_index < context_.num_options; ++option_index) | |
{ | |
const arclite_getopt_option_t* temp_option = context_.options + option_index; | |
size_t outpos; | |
char long_name[64]; | |
int chars_written = str_format(long_name, 64, "--%s", temp_option->name); | |
if(chars_written < 0) | |
return buffer; | |
outpos = size_t(chars_written); | |
switch(temp_option->type) | |
{ | |
case ARCLITE_GETOPT_TYPE_REQUIRED: | |
str_format(long_name + outpos, 64 - outpos, "=<%s>", temp_option->value_description); | |
break; | |
case ARCLITE_GETOPT_TYPE_OPTIONAL: | |
str_format(long_name + outpos, 64 - outpos, "(=%s)", temp_option->value_description); | |
break; | |
default: | |
break; | |
} | |
if(temp_option->name_short == 0x0) | |
chars_written = str_format(buffer + buffer_pos, buffer_size - buffer_pos, " %-32s - %s\n", long_name, temp_option->description); | |
else | |
chars_written = str_format( buffer + buffer_pos, buffer_size - buffer_pos, "-%c %-32s - %s\n", temp_option->name_short, long_name, temp_option->description); | |
if(chars_written < 0) | |
return buffer; | |
buffer_pos += size_t(chars_written); | |
} | |
return buffer; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file arclite_getopt.hpp | |
* @author Jacob I Tate | |
* @brief Simple getopt style command line parser with short and long options | |
*/ | |
#pragma once | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <exception> | |
#include <string> | |
/** | |
* Macro to define the end element in the options array | |
*/ | |
#define ARCLITE_GETOPT_END { 0, 0, ARCLITE_GETOPT_TYPE_NO_ARGS, 0, 0, 0, 0 } | |
/** | |
* Arg types | |
*/ | |
typedef enum : uint8_t | |
{ | |
ARCLITE_GETOPT_TYPE_NO_ARGS, ///< The option doesnt have arguments | |
ARCLITE_GETOPT_TYPE_REQUIRED, ///< The option requires an argument | |
ARCLITE_GETOPT_TYPE_OPTIONAL, ///< The option argument is optional | |
ARCLITE_GETOPT_TYPE_FLAG_SET, ///< The option is a flag or val that is to be set | |
ARCLITE_GETOPT_TYPE_FLAG_AND, ///< The option is a flag or val that is to be and'd | |
ARCLITE_GETOPT_TYPE_FLAG_OR ///< The option is a flag or val that is to be or'd | |
} arclite_getopt_type_t; | |
/** | |
* Get opt option for creating the actual options | |
*/ | |
typedef struct | |
{ | |
const char* name; ///< The long name of the argument | |
int name_short; ///< The short name of the argument | |
arclite_getopt_type_t type; ///< Type of the option @sa @ref arclite_getopt_type_t | |
int* flag; ///< Pointer to the flag to set, only effective if a flag op is set | |
int value; ///< If flag-type this value will be set/and'd/or'd to the flag, else it will be returned from arclite_getopt | |
const char* description; ///< Description of the option | |
const char* value_description; ///< Short description of the valid values Used for auto help generation "--my_option=<value_description_goes_here>" | |
} arclite_getopt_option_t; | |
/** | |
* Exception which will catch someone inputting | |
* ! ? + -1 | |
* As the short option | |
*/ | |
struct arclite_getopt_invalid_shortopt : public std::exception | |
{ | |
/** | |
* Constructs an arclite_getopt_invalid_shortopt object | |
* @param exception The exception message | |
*/ | |
explicit arclite_getopt_invalid_shortopt(const std::string& exception); | |
/** | |
* What the error is | |
* @return The error message | |
*/ | |
const char* what() const noexcept override; | |
private: | |
const std::string& exception_; ///< The exception string | |
}; | |
/** | |
* Get opt class | |
*/ | |
class arclite_getopt | |
{ | |
public: | |
/** | |
* @brief Constructs a getopt object | |
* | |
* @param argc argc from `int main(int argc, char** argv)` or equivalent value | |
* @param argv argv from `int main(int argc, char** argv)` or equivalent value | |
* @param options Pointer to array with options that should be checked for | |
* | |
* @sa @ref arclite_getopt::next | |
* @since Version 1.1.0 | |
*/ | |
arclite_getopt(int argc, const char** argv, const arclite_getopt_option_t* options); | |
/** | |
* Parses the argc/argv given at @ref arclite_getopt ctor. This will attempt to | |
* parse the next token in the menu context and return an id given the result of | |
* the parsing | |
* | |
* @return | |
* - '!' on error. The flag name will be stored in @ref arclite_getopt::current_opt_arg. | |
* This can be caused by missing arguments on a required argument or Argument being found | |
* when none are expected. | |
* - '?' if item was an unrecognized option, @ref arclite_getopt::current_opt_arg will be set to the item | |
* - '+' if item was no option, @ref arclite_getopt::current_opt_arg will be set to item | |
* - '0' if the opt was a flag and it was set. @ref arclite_getopt::current_opt_arg will be set to flag-name | |
* - -1 if there are no more items to request | |
*/ | |
int next(); | |
/** | |
* Retrieves the current options argument assuming it has one. In the case it doesnt | |
* a nullptr will be returned. | |
* | |
* @return The option argument if one is to be found, nullptr otherwise | |
*/ | |
const char* current_opt_arg(); | |
/** | |
* Builds a help string based on the values in the original @a arclite_getopt_option_t | |
* | |
* @param buffer The buffer to place the help string in | |
* @param buffer_size The size of the buffer | |
* | |
* @return The help string | |
*/ | |
const char* create_help_string(char* buffer, size_t buffer_size); | |
private: | |
/** | |
* <b>For internal use<\b> | |
* | |
* Context for use while parsing the objects must be initialized by @ref arclite_getopt_create_context. | |
* In the case this is reused reinitialization is completed via @ref arclite_getopt_create_context again. | |
*/ | |
typedef struct | |
{ | |
int argc; ///< Argc from main | |
const char** argv; ///< Argv from main | |
const arclite_getopt_option_t* options; ///< Command options | |
int num_options; ///< Number of options in the command options array | |
int current_index; ///< Current index in the iteration | |
const char* current_opt_arg; ///< Used to return values to @ref arclite_getopt_next | |
} arclite_getopt_context_t; | |
arclite_getopt_context_t context_; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "arclite_getopt.h" | |
static const arclite_getopt_option_t option_list[] = | |
{ | |
{ "help", 'h', ARCLITE_GETOPT_TYPE_NO_ARGS, nullptr, 'h', "print this help text", nullptr }, | |
{ "version", 'v', ARCLITE_GETOPT_TYPE_NO_ARGS, nullptr, 'v', "prints the version text", nullptr }, | |
ARCLITE_GETOPT_END | |
}; | |
int main(int argc,const char** argv) | |
{ | |
arclite_getopt context(argc, argv, option_list); | |
int opt; | |
while((opt = context.next()) != -1) | |
{ | |
switch(opt) | |
{ | |
case 'v': | |
{ | |
//std::cout << "Version: " << Version::current().asLongStr() << std::endl; | |
return 0; | |
} | |
case 'h': | |
{ | |
char buffer[2048]; | |
printf( "%s\n", context.create_help_string(buffer, sizeof( buffer ) ) ); | |
return 0; | |
} | |
default: | |
{ | |
break; | |
} | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment