Riyyi
3 years ago
commit
8a1fb689bd
10 changed files with 764 additions and 0 deletions
@ -0,0 +1,38 @@ |
|||||||
|
# -*- yaml -*- |
||||||
|
|
||||||
|
--- |
||||||
|
BasedOnStyle: WebKit |
||||||
|
IndentWidth: 4 |
||||||
|
--- |
||||||
|
Language: Cpp |
||||||
|
|
||||||
|
AlignAfterOpenBracket: Align |
||||||
|
AlignEscapedNewlines: Left |
||||||
|
AlignOperands: Align |
||||||
|
AlignTrailingComments: true |
||||||
|
|
||||||
|
AllowAllArgumentsOnNextLine: false |
||||||
|
AllowAllConstructorInitializersOnNextLine: true |
||||||
|
AllowAllParametersOfDeclarationOnNextLine: false |
||||||
|
AllowShortLambdasOnASingleLine: All |
||||||
|
|
||||||
|
AlwaysBreakTemplateDeclarations: Yes |
||||||
|
|
||||||
|
BraceWrapping: |
||||||
|
AfterEnum: false |
||||||
|
AfterFunction: true |
||||||
|
BeforeCatch: true |
||||||
|
BeforeElse: true |
||||||
|
BeforeLambdaBody: false |
||||||
|
SplitEmptyRecord: false |
||||||
|
BreakBeforeBraces: Custom |
||||||
|
BreakInheritanceList: BeforeComma |
||||||
|
|
||||||
|
SpaceAfterTemplateKeyword: false |
||||||
|
SpaceInEmptyBlock: false |
||||||
|
NamespaceIndentation: None |
||||||
|
FixNamespaceComments: true |
||||||
|
Standard: c++17 |
||||||
|
TabWidth: 4 |
||||||
|
UseTab: AlignWithSpaces |
||||||
|
... |
@ -0,0 +1,7 @@ |
|||||||
|
# Directories |
||||||
|
|
||||||
|
.cache/ |
||||||
|
.clangd/ |
||||||
|
build/ |
||||||
|
|
||||||
|
# Files |
@ -0,0 +1,74 @@ |
|||||||
|
# User config between these lines |
||||||
|
# ------------------------------------------ |
||||||
|
|
||||||
|
# Set project name |
||||||
|
set(PROJECT "stowage") |
||||||
|
# Set debugging, ON/OFF |
||||||
|
set(DEBUG "ON") |
||||||
|
|
||||||
|
# ------------------------------------------ |
||||||
|
|
||||||
|
# Add 'make run' target |
||||||
|
add_custom_target(run |
||||||
|
COMMAND ${PROJECT} |
||||||
|
) |
||||||
|
|
||||||
|
# ------------------------------------------ |
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.16) |
||||||
|
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) |
||||||
|
|
||||||
|
# Check if the build should include debugging symbols |
||||||
|
option(DEBUG "" ${DEBUG}) |
||||||
|
if(DEBUG) |
||||||
|
# cmake -DDEBUG=on .. && make |
||||||
|
message("--- Debug ---") |
||||||
|
set(CMAKE_BUILD_TYPE "Debug") |
||||||
|
|
||||||
|
# -Og = Optimizations that do not interfere with debugging |
||||||
|
# -Wall = All warnings about contructions that are easily avoidable |
||||||
|
# -Wextra = Extra warning flags not covered by -Wall |
||||||
|
# -g = Produce debugging information in OS's native format |
||||||
|
# -pg = Generate profile information for analysis with gprof |
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -Wall -Wextra -g -pg") |
||||||
|
# gprof <PROJECT> gmon.out > profile-data.txt |
||||||
|
else() |
||||||
|
# cmake -DDEBUG=off .. && make |
||||||
|
message("--- Release ---") |
||||||
|
set(CMAKE_BUILD_TYPE "Release") |
||||||
|
|
||||||
|
# -O3 = Optimizations that increases compilation time and performance |
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") |
||||||
|
endif() |
||||||
|
|
||||||
|
# Include all headers |
||||||
|
include_directories( |
||||||
|
"src" |
||||||
|
"test" |
||||||
|
) |
||||||
|
|
||||||
|
# Define source files |
||||||
|
file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp") |
||||||
|
set(PROJECT_SOURCES ${PROJECT_SOURCES}) |
||||||
|
|
||||||
|
# Define test source files |
||||||
|
file(GLOB_RECURSE TEST_SOURCES "test/*.cpp") |
||||||
|
file(GLOB_RECURSE MAIN_SOURCES "src/*/*.cpp") |
||||||
|
set(TEST_SOURCES ${TEST_SOURCES} ${MAIN_SOURCES}) |
||||||
|
|
||||||
|
# ------------------------------------------ |
||||||
|
|
||||||
|
project(${PROJECT}) |
||||||
|
set(CMAKE_CXX_STANDARD 17) |
||||||
|
|
||||||
|
add_executable(${PROJECT} ${PROJECT_SOURCES}) |
||||||
|
target_link_libraries(${PROJECT}) |
||||||
|
|
||||||
|
# ------------------------------------------ |
||||||
|
|
||||||
|
project(test) |
||||||
|
set(CMAKE_CXX_STANDARD 17) |
||||||
|
|
||||||
|
add_executable(test ${TEST_SOURCES}) |
||||||
|
target_link_libraries(test) |
@ -0,0 +1,74 @@ |
|||||||
|
* Stowage |
||||||
|
|
||||||
|
Needed information for generation: |
||||||
|
- short option |
||||||
|
- long option |
||||||
|
- short description (for usage ouput) |
||||||
|
- long description (for manpage output) |
||||||
|
|
||||||
|
* Option Parsing |
||||||
|
|
||||||
|
Parsed from left to right. |
||||||
|
|
||||||
|
** Types |
||||||
|
|
||||||
|
- Short option |
||||||
|
- Long option |
||||||
|
- Argument to option |
||||||
|
- Non-option parameter (default argument) |
||||||
|
|
||||||
|
*** Short Option |
||||||
|
|
||||||
|
'-' followed by short option character. |
||||||
|
If option has required argument, directly after OR separated by white-space. |
||||||
|
Optional argument must be directly after the option character. |
||||||
|
|
||||||
|
Possible to write several short options after one '-', |
||||||
|
if all (except last) do not have *required* or *optional* arguments. |
||||||
|
|
||||||
|
*** Long Option |
||||||
|
|
||||||
|
'--' followed by long option name. |
||||||
|
If option has a required argument, directly after, separated by '=' OR white-space. |
||||||
|
Optional argument must be directly after the option, separated by '='. |
||||||
|
|
||||||
|
*** Non-option Parameter |
||||||
|
|
||||||
|
Each parameter not starting with '-' and not a required argument of a previous option, |
||||||
|
is a non-option parameter. |
||||||
|
|
||||||
|
Each parameter after a '--' parameter is always interpreted as a non-option parameter. |
||||||
|
|
||||||
|
* Examples |
||||||
|
|
||||||
|
#+BEGIN_SRC shell-script |
||||||
|
./stowage -Fals |
||||||
|
./stowage -F -a -l -s |
||||||
|
|
||||||
|
./stowage --file --add --pull --push |
||||||
|
|
||||||
|
./stowage -r filename |
||||||
|
./stowage -rfilename |
||||||
|
./stowage --remove=filename |
||||||
|
./stowage --remove filename |
||||||
|
|
||||||
|
./stowage --remove filename other stuff |
||||||
|
./stowage --remove filename -- other stuff |
||||||
|
#+END_SRC |
||||||
|
|
||||||
|
** Multiple of the same options |
||||||
|
|
||||||
|
#+BEGIN_SRC shell-script |
||||||
|
./stowage -e pattern1 -epattern2 --regexp=pattern3 --regexp pattern4 |
||||||
|
#+END_SRC |
||||||
|
|
||||||
|
* TODO |
||||||
|
|
||||||
|
- after first non option, go into no-option mode |
||||||
|
- support '--' to go into no-option mode |
||||||
|
- add multi-option support, vectors! |
||||||
|
- support argument parsing + storing |
||||||
|
- add addOption overloads |
||||||
|
- generate usage string |
||||||
|
- generate man page string |
||||||
|
- parse() function to return bool true if any error has occured |
@ -0,0 +1,39 @@ |
|||||||
|
// #include <cstddef> // size_t
|
||||||
|
// #include <cstdlib> // maloc, free
|
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "util/argparser.h" |
||||||
|
|
||||||
|
// void* operator new(size_t size)
|
||||||
|
// {
|
||||||
|
// std::cout << "Allocating '" << size << "' bytes" << std::endl;
|
||||||
|
// return std::malloc(size);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void operator delete(void* pointer, size_t size)
|
||||||
|
// {
|
||||||
|
// std::cout << "Freeing '" << size << "' bytes" << std::endl;
|
||||||
|
// free(pointer);
|
||||||
|
// }
|
||||||
|
|
||||||
|
int main(int argc, const char* argv[]) |
||||||
|
{ |
||||||
|
bool pattern = false; |
||||||
|
std::string stringArg1 = "default value"; |
||||||
|
std::string stringArg2 = "nothing"; |
||||||
|
|
||||||
|
Util::ArgParser parser; |
||||||
|
// parser.setExitOnFirstError(false);
|
||||||
|
// parser.setErrorMessages(false);
|
||||||
|
|
||||||
|
parser.addOption(pattern, 'e', "regexp", "search pattern", "Use ${U}PATTERNS${N} as the patterns."); |
||||||
|
parser.addOption(stringArg1, 'a', "arg1", "test argument", "Test argument manpage description.", "TEST", Util::ArgParser::Required::Yes); |
||||||
|
parser.addOption(stringArg2, 'b', "arg2", "optional argument", "Option with optional argument", "TEST", Util::ArgParser::Required::Optional); |
||||||
|
parser.parse(argc, argv); |
||||||
|
|
||||||
|
printf(" Pattern: {%d}\n", pattern); |
||||||
|
printf(" Arg1: {%s}\n", stringArg1.data()); |
||||||
|
printf(" Arg2: {%s}\n", stringArg2.data()); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,281 @@ |
|||||||
|
#include <algorithm> // std::find_if |
||||||
|
#include <cstdio> // printf |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/argparser.h" |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
ArgParser::ArgParser() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
ArgParser::~ArgParser() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::printOptionError(char name, Error error) |
||||||
|
{ |
||||||
|
char tmp[] { name, '\0' }; |
||||||
|
printOptionError(tmp, error, false); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::printOptionError(const char* name, Error error, bool longName) |
||||||
|
{ |
||||||
|
if (!m_errorMessages) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (error == Error::InvalidOption) { |
||||||
|
printf("%s: invalid option -- '%s'\n", m_name, name); |
||||||
|
} |
||||||
|
else if (error == Error::UnrecognizedOption) { |
||||||
|
printf("%s: unrecognized option -- '%s'\n", m_name, name); |
||||||
|
} |
||||||
|
else if (error == Error::DoesntAllowArgument) { |
||||||
|
printf("%s: option '--%s' doesn't allow an argument\n", m_name, name); |
||||||
|
} |
||||||
|
else if (error == Error::RequiresArgument) { |
||||||
|
if (longName) { |
||||||
|
printf("%s: option '--%s' requires an argument", m_name, name); |
||||||
|
} |
||||||
|
else { |
||||||
|
printf("%s: option requires an argument -- '%s'\n", m_name, name); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: Print command usage, if it's enabled.
|
||||||
|
} |
||||||
|
|
||||||
|
// Required: directly after || separated by space
|
||||||
|
// Optional: directly after
|
||||||
|
bool ArgParser::parseShortOption(std::string_view option, std::string_view next) |
||||||
|
{ |
||||||
|
bool result = true; |
||||||
|
|
||||||
|
printf("Parsing short option: '%s'\n", option.data()); |
||||||
|
|
||||||
|
char c; |
||||||
|
std::string_view value; |
||||||
|
for (std::string_view::size_type i = 0; i < option.size(); ++i) { |
||||||
|
c = option.at(i); |
||||||
|
printf("short '%c'\n", c); |
||||||
|
|
||||||
|
auto foundOption = std::find_if(m_options.begin(), m_options.end(), [&c](Option& it) -> bool { |
||||||
|
return it.shortName == c; |
||||||
|
}); |
||||||
|
|
||||||
|
// Option does not exist
|
||||||
|
if (foundOption == m_options.cend()) { |
||||||
|
printOptionError(c, Error::InvalidOption); |
||||||
|
|
||||||
|
result = false; |
||||||
|
if (m_exitOnFirstError) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (foundOption->requiresArgument == Required::No) { |
||||||
|
// FIXME: Figure out why providing a nullptr breaks the lambda here.
|
||||||
|
foundOption->acceptValue(""); |
||||||
|
} |
||||||
|
else if (foundOption->requiresArgument == Required::Yes) { |
||||||
|
value = option.substr(i + 1); |
||||||
|
if (value.empty() && next.empty()) { |
||||||
|
foundOption->error = Error::RequiresArgument; |
||||||
|
printOptionError(c, Error::RequiresArgument); |
||||||
|
|
||||||
|
result = false; |
||||||
|
if (m_exitOnFirstError) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (!value.empty()) { |
||||||
|
foundOption->acceptValue(value.data()); |
||||||
|
} |
||||||
|
else if (next[0] == '-') { |
||||||
|
foundOption->error = Error::RequiresArgument; |
||||||
|
printOptionError(c, Error::RequiresArgument); |
||||||
|
|
||||||
|
result = false; |
||||||
|
if (m_exitOnFirstError) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
foundOption->acceptValue(next.data()); |
||||||
|
m_optionIndex++; |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
else if (foundOption->requiresArgument == Required::Optional) { |
||||||
|
value = option.substr(i + 1); |
||||||
|
if (!value.empty()) { |
||||||
|
foundOption->acceptValue(value.data()); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
// Required: directly after, separated by '=' || separated by space
|
||||||
|
// Optional: directly after, separated by '='
|
||||||
|
bool ArgParser::parseLongOption(std::string_view option, std::string_view next) |
||||||
|
{ |
||||||
|
bool result = true; |
||||||
|
|
||||||
|
std::string_view name = option.substr(0, option.find_first_of('=')); |
||||||
|
std::string_view value = option.substr(option.find_first_of('=') + 1); |
||||||
|
|
||||||
|
bool argumentProvided = true; |
||||||
|
if (name.compare(value) == 0 && option.find('=') == std::string_view::npos) { |
||||||
|
argumentProvided = false; |
||||||
|
} |
||||||
|
|
||||||
|
printf("Parsing long option: '%s' with value '%s'\n", name.data(), argumentProvided ? value.data() : ""); |
||||||
|
|
||||||
|
auto foundOption = std::find_if(m_options.begin(), m_options.end(), [&name](Option& it) -> bool { |
||||||
|
return it.longName == name; |
||||||
|
}); |
||||||
|
|
||||||
|
if (foundOption == m_options.cend()) { |
||||||
|
foundOption->error = Error::UnrecognizedOption; |
||||||
|
printOptionError(name.data(), Error::UnrecognizedOption); |
||||||
|
|
||||||
|
result = false; |
||||||
|
if (m_exitOnFirstError) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (argumentProvided) { |
||||||
|
if (foundOption->requiresArgument == Required::No) { |
||||||
|
foundOption->error = Error::DoesntAllowArgument; |
||||||
|
printOptionError(name.data(), Error::DoesntAllowArgument); |
||||||
|
|
||||||
|
result = false; |
||||||
|
if (m_exitOnFirstError) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (foundOption->requiresArgument == Required::Yes) { |
||||||
|
foundOption->acceptValue(value.data()); |
||||||
|
} |
||||||
|
else if (foundOption->requiresArgument == Required::Optional) { |
||||||
|
foundOption->acceptValue(value.data()); |
||||||
|
} |
||||||
|
} |
||||||
|
else if (!next.empty() && foundOption->requiresArgument == Required::Yes) { |
||||||
|
if (next[0] == '-') { |
||||||
|
foundOption->error = Error::RequiresArgument; |
||||||
|
printOptionError(name.data(), Error::RequiresArgument); |
||||||
|
|
||||||
|
result = false; |
||||||
|
if (m_exitOnFirstError) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
foundOption->acceptValue(next.data()); |
||||||
|
m_optionIndex++; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (foundOption->requiresArgument == Required::Yes) { |
||||||
|
foundOption->error = Error::RequiresArgument; |
||||||
|
printOptionError(name.data(), Error::RequiresArgument); |
||||||
|
|
||||||
|
result = false; |
||||||
|
if (m_exitOnFirstError) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
// FIXME: Figure out why providing a nullptr breaks the lambda here.
|
||||||
|
foundOption->acceptValue(""); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
bool ArgParser::parse(int argc, const char* argv[]) |
||||||
|
{ |
||||||
|
// Get program name
|
||||||
|
m_name = argv[0] + std::string_view(argv[0]).find_last_of('/') + 1; |
||||||
|
|
||||||
|
printf("name: %s\n", m_name); |
||||||
|
|
||||||
|
std::string_view argument; |
||||||
|
std::string_view next; |
||||||
|
for (; m_optionIndex < argc; ++m_optionIndex) { |
||||||
|
printf("argv[%d]: %s\n", m_optionIndex, argv[m_optionIndex]); |
||||||
|
|
||||||
|
argument = argv[m_optionIndex]; |
||||||
|
if (m_optionIndex + 1 < argc && argv[m_optionIndex + 1][0] != '-') { |
||||||
|
next = argv[m_optionIndex + 1]; |
||||||
|
} |
||||||
|
else { |
||||||
|
next = {}; |
||||||
|
} |
||||||
|
|
||||||
|
// Long Option
|
||||||
|
if (argument[0] == '-' && argument[1] == '-') { |
||||||
|
argument = argument.substr(argument.find_first_not_of('-')); |
||||||
|
parseLongOption(argument, next); |
||||||
|
} |
||||||
|
// Short Option
|
||||||
|
else if (argument[0] == '-') { |
||||||
|
argument = argument.substr(argument.find_first_not_of('-')); |
||||||
|
parseShortOption(argument, next); |
||||||
|
} |
||||||
|
// Argument
|
||||||
|
else { |
||||||
|
printf("-> argu: '%s'", argument.data()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addOption(Option&& option) |
||||||
|
{ |
||||||
|
m_options.push_back(option); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString) |
||||||
|
{ |
||||||
|
Option option { |
||||||
|
shortName, |
||||||
|
longName, |
||||||
|
nullptr, |
||||||
|
usageString, |
||||||
|
manString, |
||||||
|
Required::No, |
||||||
|
[&value](const char*) -> bool { |
||||||
|
value = true; |
||||||
|
return true; |
||||||
|
} |
||||||
|
}; |
||||||
|
addOption(std::move(option)); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addOption(std::string& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||||
|
{ |
||||||
|
Option option { |
||||||
|
shortName, |
||||||
|
longName, |
||||||
|
argumentName, |
||||||
|
usageString, |
||||||
|
manString, |
||||||
|
requiresArgument, |
||||||
|
[&value](const char* a) -> bool { |
||||||
|
value = a; |
||||||
|
return true; |
||||||
|
} |
||||||
|
}; |
||||||
|
addOption(std::move(option)); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,75 @@ |
|||||||
|
#ifndef ARG_PARSER_H |
||||||
|
#define ARG_PARSER_H |
||||||
|
|
||||||
|
#include <functional> |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
class ArgParser final { |
||||||
|
public: |
||||||
|
ArgParser(); |
||||||
|
virtual ~ArgParser(); |
||||||
|
|
||||||
|
enum class Required { |
||||||
|
No, |
||||||
|
Yes, |
||||||
|
Optional, |
||||||
|
}; |
||||||
|
|
||||||
|
enum class Error { |
||||||
|
None, |
||||||
|
InvalidOption, // For short options
|
||||||
|
UnrecognizedOption, // For long options
|
||||||
|
DoesntAllowArgument, |
||||||
|
RequiresArgument, |
||||||
|
}; |
||||||
|
|
||||||
|
struct Option { |
||||||
|
char shortName { 0 }; |
||||||
|
const char* longName { nullptr }; |
||||||
|
const char* argumentName { nullptr }; |
||||||
|
const char* usageString { nullptr }; |
||||||
|
const char* manString { nullptr }; |
||||||
|
Required requiresArgument; |
||||||
|
std::function<bool(const char*)> acceptValue; |
||||||
|
|
||||||
|
Error error = Error::None; |
||||||
|
}; |
||||||
|
|
||||||
|
struct Argument { |
||||||
|
}; |
||||||
|
|
||||||
|
bool parse(int argc, const char* argv[]); |
||||||
|
|
||||||
|
void addOption(Option&& option); |
||||||
|
void addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString); |
||||||
|
void addOption(std::string& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||||
|
|
||||||
|
void setOptionIndex(int index) { m_optionIndex = index; } |
||||||
|
void setExitOnFirstError(bool state) { m_exitOnFirstError = state; } |
||||||
|
void setErrorMessages(bool state) { m_errorMessages = state; } |
||||||
|
void setNonOptionAfterFirst(bool state) { m_nonOptionAfterFirst = state; } |
||||||
|
|
||||||
|
private: |
||||||
|
void printOptionError(char name, Error error); |
||||||
|
void printOptionError(const char* name, Error error, bool longName = true); |
||||||
|
bool parseShortOption(std::string_view option, std::string_view next); |
||||||
|
bool parseLongOption(std::string_view option, std::string_view next); |
||||||
|
|
||||||
|
int m_optionIndex { 1 }; |
||||||
|
bool m_exitOnFirstError { true }; |
||||||
|
bool m_errorMessages { true }; |
||||||
|
// TODO: Implement this, maybe combine with error messages flag, enum class? or bitfield
|
||||||
|
bool m_nonOptionAfterFirst { false }; |
||||||
|
|
||||||
|
const char* m_name; |
||||||
|
std::vector<Option> m_options; |
||||||
|
std::vector<Argument> m_arguments; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util
|
||||||
|
|
||||||
|
#endif // ARG_PARSER_H
|
@ -0,0 +1,18 @@ |
|||||||
|
#ifndef TEST_H |
||||||
|
#define TEST_H |
||||||
|
|
||||||
|
#include <cstdio> // printf |
||||||
|
#include <iostream> // cout |
||||||
|
|
||||||
|
#define EXPECT(x) \ |
||||||
|
if (!(x)) { \
|
||||||
|
printf("FAIL: %s:%d: EXPECT(%s) failed\n", __FILE__, __LINE__, #x); \
|
||||||
|
} |
||||||
|
|
||||||
|
#define EXPECT_EQ(a, b) \ |
||||||
|
if (a != b) { \
|
||||||
|
std::cout << "FAIL: " << __FILE__ << ":" << __LINE__ \
|
||||||
|
<< ": EXPECT_EQ(" << #a << ", " << #b ") failed with lhs=" << a << " and rhs=" << b << std::endl; \
|
||||||
|
} |
||||||
|
|
||||||
|
#endif // TEST_H
|
@ -0,0 +1,157 @@ |
|||||||
|
#include <cstdio> // fopen, printf, stdout |
||||||
|
#include <string> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "macro.h" |
||||||
|
#include "util/argparser.h" |
||||||
|
|
||||||
|
FILE* output; |
||||||
|
FILE* null; |
||||||
|
|
||||||
|
bool runParser(std::vector<const char*> arguments, std::function<void(Util::ArgParser&)> initializer = {}) |
||||||
|
{ |
||||||
|
stdout = null; |
||||||
|
|
||||||
|
Util::ArgParser parser; |
||||||
|
if (initializer) { |
||||||
|
initializer(parser); |
||||||
|
} |
||||||
|
|
||||||
|
arguments.insert(arguments.begin(), "app"); |
||||||
|
auto result = parser.parse(arguments.size(), arguments.data()); |
||||||
|
|
||||||
|
stdout = output; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
int main(int, const char*[]) |
||||||
|
{ |
||||||
|
output = stdout; |
||||||
|
null = fopen("/dev/null", "w"); // Windows: nul
|
||||||
|
|
||||||
|
printf("Test project\n"); |
||||||
|
|
||||||
|
// No arguments
|
||||||
|
{ |
||||||
|
auto result = runParser({}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
} |
||||||
|
|
||||||
|
// Bool options
|
||||||
|
{ |
||||||
|
// Short option
|
||||||
|
bool boolOpt1 = false; |
||||||
|
auto result = runParser({ "-b" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
} |
||||||
|
{ |
||||||
|
// Short option, not given
|
||||||
|
bool boolOpt1 = false; |
||||||
|
auto result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, false); |
||||||
|
} |
||||||
|
{ |
||||||
|
// Long option
|
||||||
|
bool boolOpt1 = false; |
||||||
|
auto result = runParser({ "--bool" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
} |
||||||
|
{ |
||||||
|
// Long option, not given
|
||||||
|
bool boolOpt1 = false; |
||||||
|
auto result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, false); |
||||||
|
} |
||||||
|
{ |
||||||
|
// Allow both short and long option, provide short
|
||||||
|
bool boolOpt1 = false; |
||||||
|
auto result = runParser({ "-b" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
} |
||||||
|
{ |
||||||
|
// Allow both short and long option, provide long
|
||||||
|
bool boolOpt1 = false; |
||||||
|
auto result = runParser({ "--bool" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
} |
||||||
|
{ |
||||||
|
// Allow both short and long option, provide both
|
||||||
|
bool boolOpt1 = false; |
||||||
|
auto result = runParser({ "-b", "--bool" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
} |
||||||
|
|
||||||
|
// ..
|
||||||
|
{ |
||||||
|
//
|
||||||
|
bool boolOpt1 = false; |
||||||
|
std::string stringOpt1 = ""; |
||||||
|
auto result = runParser({ "-b", "something", "-s", "my-value" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
} |
||||||
|
|
||||||
|
// // bool tests
|
||||||
|
// test('o', "option", { "-o" }, true);
|
||||||
|
// test('o', "option", { "-n" }, false);
|
||||||
|
// test('o', "option", { "--option" }, true);
|
||||||
|
// test('o', "option", { "--noexist" }, false);
|
||||||
|
|
||||||
|
// // string tests
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Yes, { "-o", "my-argument" }, "my-argument", 0);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Optional, { "-o", "my-argument" }, {}, 0);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::No, { "-o", "my-argument" }, {}, 0);
|
||||||
|
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Yes, { "-omy-argument" }, "my-argument", 0);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Optional, { "-omy-argument" }, "my-argument", 0);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::No, { "-omy-argument" }, {}, 0);
|
||||||
|
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Yes, { "--option", "my-argument" }, "my-argument", 0);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Optional, { "--option", "my-argument" }, {}, 0);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::No, { "--option", "my-argument" }, {}, 0);
|
||||||
|
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Yes, { "--option=my-argument" }, "my-argument", 0);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Optional, { "--option=my-argument" }, "my-argument", 0);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::No , { "--option=my-argument" }, {}, 0);
|
||||||
|
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Yes, { "-o", "my-argument" }, "not-same", -1);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Yes, { "-omy-argument" }, "not-same", -1);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Optional, { "-omy-argument" }, "not-same", -1);
|
||||||
|
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Yes, { "--option", "my-argument" }, "not-same", -1);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Yes, { "--option=my-argument" }, "not-same", -1);
|
||||||
|
// test('o', "option", Util::ArgParser::Required::Optional, { "--option=my-argument" }, "not-same", -1);
|
||||||
|
|
||||||
|
// ./help -o something -a my-value
|
||||||
|
// -a has required argument, but something should stop option parsing
|
||||||
|
|
||||||
|
printf("Completed running tests\n"); |
||||||
|
|
||||||
|
fclose(null); |
||||||
|
return 0; |
||||||
|
} |
Loading…
Reference in new issue