Initial commit
This commit is contained in:
@@ -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)
|
||||||
+74
@@ -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
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
build/compile_commands.json
|
||||||
@@ -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
|
||||||
+157
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user