poafloc

Parser Of Arguments For Lines Of Commands
git clone git://git.dimitrijedobrota.com/poafloc.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING

commit 7ee634ae13bd60f8789cfb1adc24cf79ede34d08
parent 5c4cf9d0404e3ccdfd69206e6d9faeef1203666a
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Fri, 23 May 2025 15:31:29 +0200

Start of a rewrite

Diffstat:
M .clang-format | +++++++++++++++ ---------
M .clang-tidy | ++++++++++++++++++++++++++++++ -----------------------
M BUILDING.md | ++++
M CMakeLists.txt | ++++++ ----
M CMakePresets.json | ++ --
M cmake/coverage.cmake | +++++++ --
M cmake/install-config.cmake | ++++ -
M cmake/lint.cmake | +++ ---
M example/CMakeLists.txt | +++ ----
A example/example.cpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D example/example_c.c | ---------------------------------------------------------------------------------
D example/example_cpp.cpp | ---------------------------------------------------------------------------------
D include/poafloc/poafloc.h | ---------------------------------------------------------------------------------
M include/poafloc/poafloc.hpp | +++++++++++++++++++++++++++++++++++++++++++ ---------------------------------------
D source/c_bindings.cpp | -----------------------------------------------
D source/help.cpp | ---------------------------------------------------------------------------------
M source/poafloc.cpp | ---------------------------------------------------------------------------------
D source/trie.cpp | --------------------------------------------------------
M test/CMakeLists.txt | ++++++++++ ------------------------------------------------------
D test/source/poafloc_test.cpp | ------------------------------------------------------------------
A vcpkg-configuration.json | +++++++++++++++
M vcpkg.json | +++++++++++++ ---

22 files changed, 295 insertions(+), 1366 deletions(-)


diff --git a/ .clang-format b/ .clang-format

@@ -2,9 +2,9 @@

Language: Cpp
# BasedOnStyle: Chromium
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignAfterOpenBracket: BlockIndent
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: true
AlignConsecutiveAssignments: false
AlignConsecutiveBitFields: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right

@@ -17,9 +17,10 @@ AllowShortEnumsOnASingleLine: false

AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLoopsOnASingleLine: true
AllowShortLambdasOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true

@@ -29,16 +30,16 @@ BinPackParameters: false

BraceWrapping:
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: Always
AfterControlStatement: MultiLine
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: false
BeforeElse: true
BeforeElse: false
BeforeLambdaBody: true
BeforeWhile: false
IndentBraces: false

@@ -50,11 +51,12 @@ BreakBeforeBraces: Custom

# BreakBeforeInheritanceComma: true
BreakInheritanceList: BeforeComma
BreakBeforeTernaryOperators: true
BreakBeforeConceptDeclarations: Always
BreakConstructorInitializersBeforeComma: true
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: true
BreakStringLiterals: true
ColumnLimit: 79
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false

@@ -70,6 +72,7 @@ ForEachMacros:

- foreach
- Q_FOREACH
- BOOST_FOREACH
- BASED_FOREACH
IncludeBlocks: Regroup
IncludeCategories:
# Standard library headers come before anything else

@@ -90,6 +93,8 @@ IndentPPDirectives: AfterHash

IndentExternBlock: NoIndent
IndentWidth: 2
IndentWrappedFunctionNames: false
IndentRequiresClause: true
RequiresClausePosition: OwnLine
InsertTrailingCommas: Wrapped
JavaScriptQuotes: Double
JavaScriptWrapImports: true

@@ -98,6 +103,7 @@ MacroBlockBegin: ''

MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
RequiresExpressionIndentation: OuterScope
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true

diff --git a/ .clang-tidy b/ .clang-tidy

@@ -4,28 +4,23 @@

# modernize-use-nodiscard: too aggressive, attribute is situationally useful
Checks: "*,\
-altera-*,\
-boost*,\
-cppcoreguidelines-avoid-do-while,\
-cppcoreguidelines-pro-bounds-constant-array-index,\
-fuchsia-*,\
-llvmlibc-*,\
-*-avoid-goto,\
-*-braces-around-statements,\
-*-c-arrays,\
-*-vararg,\
-*-array*decay,\
-bugprone-argument-comment,\
-bugprone-easily-swappable-parameters,\
-cert-dcl50-cpp,\
-concurrency-mt-unsafe,\
-cppcoreguidelines-avoid-magic-numbers,\
-fuchsia-multiple-inheritance,\
-hicpp-signed-bitwise,\
-google-readability-todo,\
-llvm-header-guard,\
-llvm-include-order,\
-misc-no-recursion,\
-llvmlibc-*,\
-misc-include-cleaner,\
-misc-non-private-member-variables-in-classes,\
-modernize-use-nodiscard,\
-misc-no-recursion,\
-modernize-use-designated-initializers,\
-modernize-use-trailing-return-type,\
-readability-function-cognitive-complexity,\
-readability-magic-numbers
-readability-suspicious-call-argument,\
-*-ranges,\
-cppcoreguidelines-missing-std-forward,\
-cppcoreguidelines-rvalue-reference-param-not-moved,\
"
WarningsAsErrors: ''
CheckOptions:

@@ -86,9 +81,9 @@ CheckOptions:

- key: 'readability-identifier-naming.ConstexprVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.EnumCase'
value: 'CamelCase'
value: 'lower_case'
- key: 'readability-identifier-naming.EnumConstantCase'
value: 'CamelCase'
value: 'lower_case'
- key: 'readability-identifier-naming.FunctionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalConstantCase'

@@ -142,7 +137,7 @@ CheckOptions:

- key: 'readability-identifier-naming.PublicMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ScopedEnumConstantCase'
value: 'CamelCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StaticConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StaticVariableCase'

@@ -155,18 +150,30 @@ CheckOptions:

value: 'CamelCase'
- key: 'readability-identifier-naming.TypeAliasCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TypeAliasIgnoredRegexp'
value: 'N'
- key: 'readability-identifier-naming.TypedefCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TypeTemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.TypeTemplateParameterIgnoredRegexp'
value: 'expr-type'
- key: 'readability-identifier-naming.UnionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ValueTemplateParameterCase'
value: 'CamelCase'
value: 'lower_case'
- key: 'readability-identifier-naming.VariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.VirtualMethodCase'
value: 'lower_case'
- key: 'readability-identifier-length.IgnoredVariableNames'
value: "^[abcdxyznm]$"
- key: 'readability-identifier-length.IgnoredParameterNames'
value: "^[abcdxyznm]$"
- key: 'google-runtime-int.UnsignedTypePrefix'
value: "u"
- key: 'google-runtime-int.SignedTypePrefix'
value: "i"
- key: 'cppcoreguidelines-missing-std-forward.ForwardFunction'
value: "::based::forward"
- key: 'cppcoreguidelines-rvalue-reference-param-not-moved.MoveFunction'
value: "::based::move"
...

diff --git a/ BUILDING.md b/ BUILDING.md

@@ -1,5 +1,9 @@

# Building with CMake

## Dependencies

For a list of dependencies, please refer to [vcpkg.json](vcpkg.json).

## Build

This project doesn't require any special command-line flags to build to keep

diff --git a/ CMakeLists.txt b/ CMakeLists.txt

@@ -4,7 +4,7 @@ include(cmake/prelude.cmake)


project(
poafloc
VERSION 1.2.0
VERSION 2.0.0
DESCRIPTION "Parser Of Arguments For Lines Of Commands"
HOMEPAGE_URL "https://git.dimitrijedobrota.com/poafloc.git"
LANGUAGES C CXX

@@ -13,16 +13,18 @@ project(

include(cmake/project-is-top-level.cmake)
include(cmake/variables.cmake)

find_package(based 0.2.0 CONFIG REQUIRED)

# ---- Declare library ----

add_library(
poafloc_poafloc
source/poafloc.cpp
source/c_bindings.cpp
source/help.cpp
source/trie.cpp
# source/help.cpp
# source/trie.cpp
)
add_library(poafloc::poafloc ALIAS poafloc_poafloc)
target_link_libraries(poafloc_poafloc PUBLIC based::based)

include(GenerateExportHeader)
generate_export_header(

diff --git a/ CMakePresets.json b/ CMakePresets.json

@@ -10,7 +10,7 @@

"name": "dev-mode",
"hidden": true,
"cacheVariables": {
"package_DEVELOPER_MODE": "ON",
"poafloc_DEVELOPER_MODE": "ON",
"VCPKG_MANIFEST_FEATURES": "test"
}
},

@@ -112,7 +112,7 @@

"cacheVariables": {
"ENABLE_COVERAGE": "ON",
"CMAKE_BUILD_TYPE": "Coverage",
"CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions",
"CMAKE_CXX_FLAGS_COVERAGE": "-O0 -g --coverage -fkeep-inline-functions -fkeep-static-functions",
"CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage",
"CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage",
"CMAKE_MAP_IMPORTED_CONFIG_COVERAGE": "Coverage;RelWithDebInfo;Release;Debug;"

diff --git a/ cmake/coverage.cmake b/ cmake/coverage.cmake

@@ -1,20 +1,25 @@

# ---- Variables ----

find_program(LCOV lcov REQUIRED)
find_program(GENHTML genhtml REQUIRED)

# We use variables separate from what CTest uses, because those have
# customization issues
set(
COVERAGE_TRACE_COMMAND
lcov -c -q
${LCOV} -c -q
-o "${PROJECT_BINARY_DIR}/coverage.info"
-d "${PROJECT_BINARY_DIR}"
--include "${PROJECT_SOURCE_DIR}/*"
--capture
--all
CACHE STRING
"; separated command to generate a trace for the 'coverage' target"
)

set(
COVERAGE_HTML_COMMAND
genhtml --legend -f -q
${GENHTML} --legend -f -q
"${PROJECT_BINARY_DIR}/coverage.info"
-p "${PROJECT_SOURCE_DIR}"
-o "${PROJECT_BINARY_DIR}/coverage_html"

diff --git a/ cmake/install-config.cmake b/ cmake/install-config.cmake

@@ -1,3 +1,6 @@

include(CMakeFindDependencyMacro)
find_dependency(based)

include("${CMAKE_CURRENT_LIST_DIR}/poaflocTargets.cmake")
if(based_FOUND)
include("${CMAKE_CURRENT_LIST_DIR}/poaflocTargets.cmake")
endif()

diff --git a/ cmake/lint.cmake b/ cmake/lint.cmake

@@ -1,9 +1,9 @@

cmake_minimum_required(VERSION 3.14)

macro(default name)
if(NOT DEFINED "${name}")
set("${name}" "${ARGN}")
endif()
if(NOT DEFINED "${name}")
set("${name}" "${ARGN}")
endif()
endmacro()

default(FORMAT_COMMAND clang-format)

diff --git a/ example/CMakeLists.txt b/ example/CMakeLists.txt

@@ -11,8 +11,8 @@ endif()


add_custom_target(run-examples)

function(add_example NAME EXT)
add_executable("${NAME}" "${NAME}.${EXT}")
function(add_example NAME)
add_executable("${NAME}" "${NAME}.cpp")
target_link_libraries("${NAME}" PRIVATE poafloc::poafloc)
target_compile_features("${NAME}" PRIVATE cxx_std_20)
add_custom_target("run_${NAME}" COMMAND "${NAME}" VERBATIM)

@@ -20,7 +20,6 @@ function(add_example NAME EXT)

add_dependencies(run-examples "run_${NAME}")
endfunction()

add_example(example_c c)
add_example(example_cpp cpp)
add_example(example)

add_folders(Example)

diff --git a/ example/example.cpp b/ example/example.cpp

@@ -0,0 +1,65 @@

#include <iostream>

#include <poafloc/poafloc.hpp>

class arguments
{
std::string m_priv = "default";

public:
// NOLINTBEGIN(*non-private*)
int val = 0;
double mul = 0;
std::string name = "default";
// NOLINTEND(*non-private*)

void set_priv(std::string_view value) { m_priv = value; }

friend std::ostream& operator<<(std::ostream& ost, const arguments& args)
{
return ost << args.val << ' ' << args.mul << ' ' << args.name << ' '
<< args.m_priv;
}
};

int main()
{
using poafloc::option;
using poafloc::parser;

const auto program = parser<arguments> {
option {
"-v,--value",
&arguments::val,
},
option {
"-m,--multiply",
&arguments::mul,
},
option {
"-n,--name",
&arguments::name,
},
option {
"-p,--priv",
&arguments::set_priv,
},
};

arguments args;
std::cout << args << '\n';

program.set('v', args, "150");
std::cout << args << '\n';

program.set('m', args, "1.34");
std::cout << args << '\n';

program.set("name", args, "Hello there!");
std::cout << args << '\n';

program.set('p', args, "General Kenobi!");
std::cout << args << '\n';

return 0;
}

diff --git a/ example/example_c.c b/ example/example_c.c

@@ -1,101 +0,0 @@

#include <poafloc/poafloc.h>
#include <stdio.h>

void error(const char* message)
{
(void)fprintf(stderr, "%s\n", message);
}

typedef struct
{
const char* output_file;
const char* input_file;

int debug;
int hex;
int relocatable;
} arguments_t;

int parse_opt(int key, const char* arg, poafloc_parser_t* parser)
{
arguments_t* arguments = (arguments_t*)poafloc_parser_input(parser);

switch (key)
{
case 777:
arguments->debug = 1;
break;
case 'h':
if (arguments->relocatable) error("cannot mix -hex and -relocatable");
arguments->hex = 1;
break;
case 'r':
if (arguments->hex) error("cannot mix -hex and -relocatable");
arguments->relocatable = 1;
break;
case 'o':
arguments->output_file = arg ? arg : "stdout";
break;
case 'i':
arguments->input_file = arg;
break;
// case Parser::Key::ARG: arguments->args.push_back(arg); break;
case POAFLOC_KEY_ERROR:
(void)fprintf(stderr, "handled error\n");
break;
case POAFLOC_KEY_INIT:
arguments->input_file = "stdin";
arguments->output_file = "stdout";
default:
break;
}

return 0;
}

// clang-format off
static const poafloc_option_t options[] = {
{ 0, 'R', 0, 0, "random 0-group option"},
{ 0, 0, 0, 0, "Program mode", 1},
{"relocatable", 'r', 0, 0, "Output in relocatable format"},
{ "hex", 'h', 0, 0, "Output in hex format"},
{"hexadecimal", 0, 0, POAFLOC_OPTION_ALIAS | POAFLOC_OPTION_HIDDEN},
{ 0, 0, 0, 0, "For developers", 4},
{ "debug", 777, 0, 0, "Enable debugging mode"},
{ 0, 0, 0, 0, "Input/output", 3},
{ "output", 'o', "file", POAFLOC_OPTION_ARG_OPTIONAL, "Output file, default stdout"},
{ 0, 'i', "file", 0, "Input file"},
{ 0, 0, 0, 0, "Informational Options", -1},
{0},
};

static const poafloc_arg_t argp = {
options, parse_opt, "doc string\nother usage",
"First half of the message\vsecond half of the message"
};
// clang-format on

int main(int argc, char* argv[])
{
arguments_t arguments = {0};

if (poafloc_parse(&argp, argc, argv, 0, &arguments))
{
error("There was an error while parsing arguments");
return 1;
}

printf("Command line options:\n");
printf("\t input: %s\n", arguments.input_file);
printf("\t output: %s\n", arguments.output_file);
printf("\t hex: %d\n", arguments.hex);
printf("\t debug: %d\n", arguments.debug);
printf("\t relocatable: %d\n", arguments.relocatable);

// std::cout << "\t args: ";
// for (const auto &arg : arguments.args)
// std::cout << arg << " ";
// std::cout << std::endl;

return 0;
}

diff --git a/ example/example_cpp.cpp b/ example/example_cpp.cpp

@@ -1,107 +0,0 @@

#include <cstdint>
#include <iostream>
#include <vector>

#include <poafloc/poafloc.hpp>

using namespace poafloc; // NOLINT

void error(const std::string& message)
{
std::cerr << message << std::endl;
}

struct arguments_t
{
const char* output_file = "stdout";
const char* input_file = "stdin";

bool debug = false;
bool hex = false;
bool relocatable = false;

std::vector<const char*> args;
};

int parse_opt(int key, const char* arg, Parser* parser)
{
auto* arguments = static_cast<arguments_t*>(parser->input());

switch (key)
{
case 777:
arguments->debug = true;
break;
case 'h':
if (arguments->relocatable) error("cannot mix -hex and -relocatable");
arguments->hex = true;
break;
case 'r':
if (arguments->hex) error("cannot mix -hex and -relocatable");
arguments->relocatable = true;
break;
case 'o':
arguments->output_file = arg != nullptr ? arg : "stdout";
break;
case 'i':
arguments->input_file = arg;
break;
case Key::ARG:
arguments->args.push_back(arg);
break;
case Key::ERROR:
help(parser, stderr, STD_ERR);
break;
default:
break;
}

return 0;
}

// clang-format off
static const option_t options[] = {
{ nullptr, 'R', nullptr, 0, "random 0-group option"},
{ nullptr, 0, nullptr, 0, "Program mode", 1},
{"relocatable", 'r', nullptr, 0, "Output in relocatable format"},
{ "hex", 'h', nullptr, 0, "Output in hex format"},
{"hexadecimal", 0, nullptr, ALIAS | HIDDEN},
{ nullptr, 0, nullptr, 0, "For developers", 4},
{ "debug", 777, nullptr, 0, "Enable debugging mode"},
{ nullptr, 0, nullptr, 0, "Input/output", 3},
{ "output", 'o', "file", ARG_OPTIONAL, "Output file, default stdout"},
{ nullptr, 'i', "file", 0, "Input file"},
{ nullptr, 0, nullptr, 0, "Informational Options", -1},
{},
};

static const arg_t argp = {
options, parse_opt, "doc string\nother usage",
"First half of the message\vsecond half of the message"
};
// clang-format on

int main(int argc, char* argv[])
{
arguments_t arguments;

if (parse(&argp, argc, argv, 0, &arguments) != 0)
{
error("There was an error while parsing arguments");
return 1;
}

std::cout << "Command line options: " << std::endl;

std::cout << "\t input: " << arguments.input_file << std::endl;
std::cout << "\t output: " << arguments.output_file << std::endl;
std::cout << "\t hex: " << arguments.hex << std::endl;
std::cout << "\t debug: " << arguments.debug << std::endl;
std::cout << "\t relocatable: " << arguments.relocatable << std::endl;

std::cout << "\t args: ";
for (const auto& arg : arguments.args) std::cout << arg << " ";
std::cout << std::endl;

return 0;
}

diff --git a/ include/poafloc/poafloc.h b/ include/poafloc/poafloc.h

@@ -1,149 +0,0 @@

#ifndef POAFLOC_POAFLOC_H
#define POAFLOC_POAFLOC_H

#ifdef __cplusplus

# include <cstdio>

# include "poafloc/poafloc_export.hpp"

// NOLINTNEXTLINE
# define MANGLE_ENUM(enumn, name) name

# define ENUM_OPTION Option
# define ENUM_KEY Key
# define ENUM_HELP Help
# define ENUM_PARSE Parse

# define INIT_0 = 0
# define INIT_NULLPTR = nullptr

extern "C"
{
namespace poafloc {

#else

# include <stdio.h>

# define MANGLE_ENUM(enumn, name) POAFLOC_##enumn##_##name

# define ENUM_OPTION poafloc_option_e
# define ENUM_KEY poafloc_key_e
# define ENUM_HELP poafloc_help_e
# define ENUM_PARSE poafloc_parse_e

# define INIT_0
# define INIT_NULLPTR

#endif

struct Parser;
typedef struct Parser poafloc_parser_t; // NOLINT

typedef struct // NOLINT
POAFLOC_EXPORT
{
char const* name INIT_NULLPTR;
int key INIT_0;
char const* arg INIT_NULLPTR;
int flags INIT_0;
char const* message INIT_NULLPTR;
int group INIT_0;
} poafloc_option_t;

// NOLINTNEXTLINE
typedef int (*poafloc_parse_f)(int key,
const char* arg,
poafloc_parser_t* parser);

// NOLINTNEXTLINE
typedef struct
{
poafloc_option_t const* options INIT_NULLPTR;
poafloc_parse_f parse INIT_NULLPTR;
char const* doc INIT_NULLPTR;
char const* message INIT_NULLPTR;
} poafloc_arg_t;

enum ENUM_OPTION
{
MANGLE_ENUM(OPTION, ARG_OPTIONAL) = 0x1,
MANGLE_ENUM(OPTION, HIDDEN) = 0x2,
MANGLE_ENUM(OPTION, ALIAS) = 0x4,
};

enum ENUM_KEY
{
MANGLE_ENUM(KEY, ARG) = 0,
MANGLE_ENUM(KEY, END) = 0x1000001,
MANGLE_ENUM(KEY, NO_ARGS) = 0x1000002,
MANGLE_ENUM(KEY, INIT) = 0x1000003,
MANGLE_ENUM(KEY, SUCCESS) = 0x1000004,
MANGLE_ENUM(KEY, ERROR) = 0x1000005,
};

enum ENUM_HELP
{
MANGLE_ENUM(HELP, SHORT_USAGE) = 0x1,
MANGLE_ENUM(HELP, USAGE) = 0x2,
MANGLE_ENUM(HELP, SEE) = 0x4,
MANGLE_ENUM(HELP, LONG) = 0x8,

MANGLE_ENUM(HELP, EXIT_ERR) = 0x10,
MANGLE_ENUM(HELP, EXIT_OK) = 0x20,

MANGLE_ENUM(HELP, STD_ERR) = MANGLE_ENUM(HELP, SEE)
| MANGLE_ENUM(HELP, EXIT_ERR),

MANGLE_ENUM(HELP, STD_HELP) = MANGLE_ENUM(HELP, LONG)
| MANGLE_ENUM(HELP, EXIT_OK),

MANGLE_ENUM(HELP, STD_USAGE) = MANGLE_ENUM(HELP, USAGE)
| MANGLE_ENUM(HELP, EXIT_ERR),
};

enum ENUM_PARSE
{
MANGLE_ENUM(PARSE, NO_ERRS) = 0x1,
MANGLE_ENUM(PARSE, NO_HELP) = 0x2,
MANGLE_ENUM(PARSE, NO_EXIT) = 0x4,
MANGLE_ENUM(PARSE, SILENT) = 0x8,
MANGLE_ENUM(PARSE, IN_ORDER) = 0x10,
};

#if !defined __cplusplus || defined WITH_C_BINDINGS

void poafloc_usage(poafloc_parser_t* parser);

void poafloc_help(const poafloc_parser_t* state, FILE* stream, unsigned flags);

int poafloc_parse(const poafloc_arg_t* argp,
int argc,
char* argv[],
unsigned flags,
void* input);

void* poafloc_parser_input(poafloc_parser_t* parser);

void poafloc_failure(const poafloc_parser_t* parser,
int status,
int errnum,
const char* fmt,
...);

#endif

#undef MANGLE_ENUM
#undef ENUM_OPTION
#undef ENUM_KEY

#undef INIT_0
#undef INIT_NULLPTR

#ifdef __cplusplus
} // namespace poafloc
} // extern "C"
#endif

#endif

diff --git a/ include/poafloc/poafloc.hpp b/ include/poafloc/poafloc.hpp

@@ -1,134 +1,146 @@

#ifndef POAFLOC_POAFLOC_HPP
#define POAFLOC_POAFLOC_HPP
#pragma once

#include <array>
#include <cstdarg>
#include <memory>
#include <algorithm>
#include <concepts>
#include <functional>
#include <sstream>
#include <string>
#include <unordered_map>
#include <string_view>
#include <vector>

#include "poafloc.h"

namespace poafloc {

using option_t = poafloc_option_t;
using arg_t = poafloc_arg_t;

int parse(const arg_t* argp,
int argc,
char* argv[],
unsigned flags,
void* input) noexcept;

void usage(const Parser* parser);
void help(const Parser* parser, FILE* stream, unsigned flags);

void failure(const Parser* parser,
int status,
int errnum,
const char* fmt,
va_list args);

void failure(
const Parser* parser, int status, int errnum, const char* fmt, ...);

struct Parser
namespace poafloc
{
Parser(const Parser&) = delete;
Parser(Parser&&) = delete;
Parser& operator=(const Parser&) = delete;
Parser& operator=(Parser&&) = delete;

void* input() const { return m_input; }
unsigned flags() const { return m_flags; }
const std::string& name() const { return m_name; }

private:
friend void help(const Parser* parser, FILE* stream, unsigned flags);
friend int parse(const arg_t* argp,
int argc,
char** argv,
unsigned flags,
void* input) noexcept;

Parser(const arg_t* argp, unsigned flags, void* input);
~Parser() noexcept = default;
template<class Record>
class option_base
{
using func_t = std::function<void(Record&, std::string_view)>;
func_t m_func;

int parse(std::size_t argc, char* argv[]);
protected:
explicit option_base(func_t func)
: m_func(std::move(func))
{
}

int handle_unknown(bool shrt, const char* argv);
int handle_missing(bool shrt, const char* argv);
int handle_excess(const char* argv);
public:
void operator()(Record& record, std::string_view value) const
{
m_func(record, value);
}
};

void print_other_usages(FILE* stream) const;
void help(FILE* stream) const;
void usage(FILE* stream) const;
void see(FILE* stream) const;
template<class Record, class Type>
class option : public option_base<Record>
{
std::string m_opts;

static std::string basename(const std::string& name);
using member_type = Type Record::*;

struct help_entry_t
static auto create(member_type member)
{
help_entry_t(const char* arg,
const char* message,
int group,
bool opt = false)
: m_arg(arg)
, m_message(message)
, m_group(group)
, m_opt(opt)
return [member](Record& record, std::string_view value)
{
}

void push(char shrt) { m_opt_short.push_back(shrt); }
void push(const char* lng) { m_opt_long.push_back(lng); }
if constexpr (std::is_invocable_v<member_type, Record, std::string_view>)
{
std::invoke(member, record, value);
} else if constexpr (requires(Type tmp, std::string_view val) {
tmp = val;
})
{
std::invoke(member, record) = value;
} else {
auto istr = std::istringstream(std::string(value));
Type tmp;
istr >> tmp;
std::invoke(member, record) = tmp;
}
};
}

public:
using record_type = Record;
using value_type = Type;

option(std::string_view opts, member_type member)
: option_base<Record>(create(member))
, m_opts(opts)
{
}

bool operator<(const help_entry_t& rhs) const;
[[nodiscard]] const std::string& opts() const { return m_opts; }
};

const char* m_arg;
const char* m_message;
int m_group;
bool m_opt;
template<class Record>
class parser
{
std::vector<option_base<Record>> m_setters;
std::vector<char> m_opt_short;
std::vector<std::string> m_opt_long;

std::vector<const char*> m_opt_long;
std::vector<char> m_opt_short;
};
void process(std::string_view opts)
{
auto istr = std::istringstream(std::string(opts));
std::string opt;

m_opt_short.emplace_back('\0');
m_opt_long.emplace_back("");
while (std::getline(istr, opt, ',')) {
if (opt.size() < 2 || opt[0] != '-') {
continue;
}

if (opt[1] != '-') {
m_opt_short.back() = opt[1];
} else {
m_opt_long.back() = opt.substr(2);
}
}
}

class trie_t
public:
template<class... Args>
explicit parser(Args&&... args)
requires(std::same_as<Record, typename Args::record_type> && ...)
{
public:
trie_t() = default;
~trie_t() noexcept = default;
constexpr auto size = sizeof...(Args);
m_opt_short.reserve(size);
m_opt_long.reserve(size);
(process(args.opts()), ...);

trie_t(const trie_t&) = delete;
trie_t(trie_t&&) = delete;
trie_t& operator=(const trie_t&) = delete;
trie_t& operator=(trie_t&&) = delete;
m_setters.reserve(size);
(m_setters.emplace_back(std::forward<Args>(args)), ...);
}

bool insert(const std::string& option, int key);
int get(const std::string& option) const;
bool set(char chr, Record& record, std::string_view value) const
{
const auto itr =
std::find(std::begin(m_opt_short), std::end(m_opt_short), chr);

private:
static bool is_valid(const std::string& option);
if (itr == m_opt_short.end()) {
return false;
}

std::array<std::unique_ptr<trie_t>, 26> m_children;
int m_count = 0;
int m_key = 0;
bool m_terminal = false;
};
const auto setter =
std::begin(m_setters) + std::distance(std::begin(m_opt_short), itr);
(*setter)(record, value);
return false;
}

const arg_t* m_argp;
unsigned m_flags;
void* m_input;
bool set(std::string_view str, Record& record, std::string_view value) const
{
const auto itr =
std::find(std::begin(m_opt_long), std::end(m_opt_long), str);

std::string m_name;
if (itr == m_opt_long.end()) {
return false;
}

std::unordered_map<int, const option_t*> m_options;
std::vector<help_entry_t> m_help_entries;
trie_t m_trie;
const auto setter =
std::begin(m_setters) + std::distance(std::begin(m_opt_long), itr);
(*setter)(record, value);
return false;
}
};

} // namespace poafloc

#endif

diff --git a/ source/c_bindings.cpp b/ source/c_bindings.cpp

@@ -1,47 +0,0 @@

#include <cstdarg>

#include "poafloc/poafloc.h"
#include "poafloc/poafloc.hpp"

extern "C"
{
namespace poafloc {

int poafloc_parse(const poafloc_arg_t* argp,
int argc,
char* argv[],
unsigned flags,
void* input)
{
return parse(argp, argc, argv, flags, input);
}

void* poafloc_parser_input(poafloc_parser_t* parser)
{
return parser->input();
}

void poafloc_usage(poafloc_parser_t* parser)
{
return usage(parser);
}

void poafloc_help(const poafloc_parser_t* parser, FILE* stream, unsigned flags)
{
help(parser, stream, flags);
}

void poafloc_failure(const poafloc_parser_t* parser,
int status,
int errnum,
const char* fmt,
...)
{
std::va_list args;
va_start(args, fmt);
failure(parser, status, errnum, fmt, args);
va_end(args);
}

} // namespace poafloc
}

diff --git a/ source/help.cpp b/ source/help.cpp

@@ -1,222 +0,0 @@

#include <cstring>
#include <format>
#include <sstream>

#include "poafloc/poafloc.hpp"

namespace poafloc {

bool Parser::help_entry_t::operator<(const help_entry_t& rhs) const
{
if (m_group != rhs.m_group)
{
if ((m_group != 0) && (rhs.m_group != 0))
{
if (m_group < 0 && rhs.m_group < 0) return m_group < rhs.m_group;
if (m_group < 0 || rhs.m_group < 0) return rhs.m_group < 0;
return m_group < rhs.m_group;
}

return m_group == 0;
}

const char ch1 = !m_opt_long.empty() ? m_opt_long.front()[0]
: !m_opt_short.empty() ? m_opt_short.front()
: '0';

const char ch22 = !rhs.m_opt_long.empty() ? rhs.m_opt_long.front()[0]
: !rhs.m_opt_short.empty() ? rhs.m_opt_short.front()
: '0';

if (ch1 != ch22)
{
return ch1 < ch22;
}

if (!m_opt_long.empty() || !rhs.m_opt_long.empty())
{
return !m_opt_long.empty();
}

return std::strcmp(m_opt_long.front(), rhs.m_opt_long.front()) < 0;
}

void Parser::print_other_usages(FILE* stream) const
{
if (m_argp->doc != nullptr)
{
std::istringstream iss(m_argp->doc);
std::string str;

std::getline(iss, str, '\n');
std::ignore = std::fprintf(stream, " %s", str.c_str());

while (std::getline(iss, str, '\n'))
{
std::ignore = std::fprintf(
stream, "\n or: %s [OPTIONS...] %s", m_name.c_str(), str.c_str());
}
}
}

void Parser::help(FILE* stream) const
{
std::string msg1;
std::string msg2;

if (m_argp->message != nullptr)
{
std::istringstream iss(m_argp->message);
std::getline(iss, msg1, '\v');
std::getline(iss, msg2, '\v');
}

std::ignore = std::fprintf(stream, "Usage: %s [OPTIONS...]", m_name.c_str());
print_other_usages(stream);

if (!msg1.empty()) std::ignore = std::fprintf(stream, "\n%s", msg1.c_str());
std::ignore = std::fprintf(stream, "\n\n");

bool first = true;
for (const auto& entry : m_help_entries)
{
bool prev = false;

if (entry.m_opt_short.empty() && entry.m_opt_long.empty())
{
if (!first) std::ignore = std::putc('\n', stream);
if (entry.m_message != nullptr)
std::ignore = std::fprintf(stream, " %s:\n", entry.m_message);

continue;
}

first = false;

std::string message = " ";
for (const char shrt : entry.m_opt_short)
{
if (!prev) prev = true;
else message += ", ";

message += std::format("-{}", shrt);

if ((entry.m_arg == nullptr) || !entry.m_opt_long.empty()) continue;

if (entry.m_opt) message += std::format("[{}]", entry.m_arg);
else message += std::format(" {}", entry.m_arg);
}

if (!prev) message += " ";

for (const auto* const lng : entry.m_opt_long)
{
if (!prev) prev = true;
else message += ", ";

message += std::format("--{}", lng);

if (entry.m_arg == nullptr) continue;

if (entry.m_opt) message += std::format("[={}]", entry.m_arg);
else message += std::format("={}", entry.m_arg);
}

static const int limit = 30;
if (size(message) < limit)
message += std::string(limit - size(message), ' ');

std::ignore = std::fprintf(stream, "%s", message.c_str());

if (entry.m_message != nullptr)
{
std::istringstream iss(entry.m_message);
std::size_t count = 0;
std::string str;

std::ignore = std::fprintf(stream, " ");
while (iss >> str)
{
count += size(str);
if (count > limit)
{
std::ignore = std::fprintf(stream, "\n%*c", limit + 5, ' ');
count = size(str);
}
std::ignore = std::fprintf(stream, "%s ", str.c_str());
}
}
std::ignore = std::putc('\n', stream);
}

if (!msg2.empty())
std::ignore = std::fprintf(stream, "\n%s\n", msg2.c_str());
}

void Parser::usage(FILE* stream) const
{
static const std::size_t limit = 60;
static std::size_t count = 0;

const auto print = [&stream](const std::string& message)
{
if (count + size(message) > limit)
count = static_cast<std::size_t>(std::fprintf(stream, "\n "));

std::ignore = std::fprintf(stream, "%s", message.c_str());
count += size(message);
};

std::string message = std::format("Usage: {}", m_name);

message += " [-";
for (const auto& entry : m_help_entries)
{
if (entry.m_arg != nullptr) continue;

for (const char shrt : entry.m_opt_short) message += shrt;
}
message += "]";

std::ignore = std::fprintf(stream, "%s", message.c_str());
count = size(message);

for (const auto& entry : m_help_entries)
{
if (entry.m_arg == nullptr) continue;
for (const char shrt : entry.m_opt_short)
{
if (entry.m_opt) print(std::format(" [-{}[{}]]", shrt, entry.m_arg));
else print(std::format(" [-{} {}]", shrt, entry.m_arg));
}
}

for (const auto& entry : m_help_entries)
{
for (const char* name : entry.m_opt_long)
{
if (entry.m_arg == nullptr)
{
print(std::format(" [--{}]", name));
continue;
}

if (entry.m_opt) print(std::format(" [--{}[={}]]", name, entry.m_arg));
else print(std::format(" [--{}={}]", name, entry.m_arg));
}
}

print_other_usages(stream);
std::ignore = std::putc('\n', stream);
}

void Parser::see(FILE* stream) const
{
std::ignore =
std::fprintf(stream,
"Try '%s --help' or '%s --usage' for more information\n",
m_name.c_str(),
m_name.c_str());
}

} // namespace poafloc

diff --git a/ source/poafloc.cpp b/ source/poafloc.cpp

@@ -1,407 +0,0 @@

#include <algorithm>
#include <cstring>
#include <format>
#include <iostream>
#include <sstream>

#include "poafloc/poafloc.hpp"

namespace poafloc {

int parse(const arg_t* argp,
int argc,
char* argv[],
unsigned flags,
void* input) noexcept
{
Parser parser(argp, flags, input);
return parser.parse(static_cast<std::size_t>(argc), argv);
}

void usage(const Parser* parser)
{
help(parser, stderr, Help::STD_USAGE);
}

void help(const Parser* parser, FILE* stream, unsigned flags)
{
if ((parser == nullptr) || (stream == nullptr)) return;

if ((flags & LONG) != 0U) parser->help(stream);
else if ((flags & USAGE) != 0U) parser->usage(stream);
else if ((flags & SEE) != 0U) parser->see(stream);

if ((parser->flags() & NO_EXIT) != 0U) return;

if ((flags & EXIT_ERR) != 0U) exit(2);
if ((flags & EXIT_OK) != 0U) exit(0);
}

void failure(const Parser* parser,
int status,
int errnum,
const char* fmt,
std::va_list args)
{
(void)errnum;

std::cerr << parser->name() << ": ";
std::ignore = std::vfprintf(stderr, fmt, args); // NOLINT
std::cerr << '\n';

if (status != 0) exit(status);
}

void failure(
const Parser* parser, int status, int errnum, const char* fmt, ...)
{
std::va_list args;
va_start(args, fmt);
failure(parser, status, errnum, fmt, args);
va_end(args);
}

Parser::Parser(const arg_t* argp, unsigned flags, void* input)
: m_argp(argp)
, m_flags(flags)
, m_input(input)
{
int group = 0;
int key_last = 0;
bool hidden = false;

for (int i = 0; true; i++)
{
const auto& opt = argp->options[i]; // NOLINT

if ((opt.name == nullptr) && (opt.key == 0) && (opt.message == nullptr))
break;

if ((opt.name == nullptr) && (opt.key == 0))
{
group = opt.group != 0U ? opt.group : group + 1;
m_help_entries.emplace_back(nullptr, opt.message, group);
continue;
}

if (opt.key == 0)
{
// non alias without a key, silently ignoring
if ((opt.flags & ALIAS) == 0) continue;

// nothing to alias, silently ignoring
if (key_last == 0) continue;

// option not valid, silently ignoring
if (!m_trie.insert(opt.name, key_last)) continue;

if (hidden) continue;
if ((opt.flags & Option::HIDDEN) != 0U) continue;

m_help_entries.back().push(opt.name);
}
else
{
// duplicate key, silently ignoring
if (m_options.contains(opt.key)) continue;

if (opt.name != nullptr) m_trie.insert(opt.name, opt.key);
m_options[key_last = opt.key] = &opt;

bool const arg_opt = (opt.flags & Option::ARG_OPTIONAL) != 0U;

if ((opt.flags & ALIAS) == 0U)
{
hidden = (opt.flags & Option::HIDDEN) != 0;
if (hidden) continue;

m_help_entries.emplace_back(opt.arg, opt.message, group, arg_opt);

if (opt.name != nullptr) m_help_entries.back().push(opt.name);

if (std::isprint(opt.key) != 0U)
{
m_help_entries.back().push(static_cast<char>(opt.key & 0xFF));
}
}
else
{
// nothing to alias, silently ignoring
if (key_last == 0) continue;
if (hidden) continue;
if ((opt.flags & Option::HIDDEN) != 0U) continue;

if (opt.name != nullptr) m_help_entries.back().push(opt.name);
if (std::isprint(opt.key) != 0U)
{
m_help_entries.back().push(static_cast<char>(opt.key & 0xFF));
}
}
}
}

if ((m_flags & NO_HELP) == 0U)
{
m_help_entries.emplace_back(nullptr, "Give this help list", -1);
m_help_entries.back().push("help");
m_help_entries.back().push('?');
m_help_entries.emplace_back(nullptr, "Give a short usage message", -1);
m_help_entries.back().push("usage");
}

std::sort(begin(m_help_entries), end(m_help_entries));
}

int Parser::parse(std::size_t argc, char* argv[])
{
const std::span args(argv, argv + argc);

std::vector<const char*> args_free;
std::size_t idx = 0;
int arg_cnt = 0;
int err_code = 0;

const bool is_help = (m_flags & NO_HELP) == 0U;
const bool is_error = (m_flags & NO_ERRS) == 0U;

m_name = basename(args[0]);
m_argp->parse(Key::INIT, nullptr, this);

for (idx = 1; idx < argc; idx++)
{
if (args[idx][0] != '-')
{
if ((m_flags & IN_ORDER) != 0U)
{
err_code = m_argp->parse(Key::ARG, args[idx], this);
if (err_code != 0) goto error;
}
else args_free.push_back(args[idx]);

arg_cnt++;
continue;
}

// stop parsing options, rest are normal arguments
if (std::strcmp(args[idx], "--") == 0) break;

if (args[idx][1] != '-')
{ // short option
const std::string opt = args[idx] + 1;

// loop over ganged options
for (std::size_t j = 0; opt[j] != 0; j++)
{
const char key = opt[j];

if (is_help && key == '?')
{
if (is_error) ::poafloc::help(this, stderr, STD_HELP);
continue;
}

if (!m_options.contains(key))
{
err_code = handle_unknown(false, args[idx]);
goto error;
}

const auto* option = m_options[key];
bool const is_opt = (option->flags & ARG_OPTIONAL) != 0;

if (option->arg == nullptr)
{
err_code = m_argp->parse(key, nullptr, this);
if (err_code != 0) goto error;
continue;
}

if (opt[j + 1] != 0)
{
err_code = m_argp->parse(key, opt.substr(j + 1).c_str(), this);
if (err_code != 0) goto error;
break;
}

if (is_opt)
{
err_code = m_argp->parse(key, nullptr, this);
if (err_code != 0) goto error;
continue;
}

if (idx + 1 != argc)
{
err_code = m_argp->parse(key, args[++idx], this);
if (err_code != 0) goto error;
break;
}

err_code = handle_missing(true, args[idx]);
goto error;
}
}
else
{ // long option
const std::string tmp = args[idx] + 2;
const auto pos = tmp.find_first_of('=');
const auto opt = tmp.substr(0, pos);
const auto arg = tmp.substr(pos + 1);

if (is_help && opt == "help")
{
if (pos != std::string::npos)
{
err_code = handle_excess(args[idx]);
goto error;
}

if (!is_error) continue;
::poafloc::help(this, stderr, STD_HELP);
}

if (is_help && opt == "usage")
{
if (pos != std::string::npos)
{
err_code = handle_excess(args[idx]);
goto error;
}

if (!is_error) continue;
::poafloc::help(this, stderr, STD_USAGE);
}

const int key = m_trie.get(opt);
if (key == 0)
{
err_code = handle_unknown(false, args[idx]);
goto error;
}

const auto* option = m_options[key];
if (pos != std::string::npos && option->arg == nullptr)
{
err_code = handle_excess(args[idx]);
goto error;
}

const bool is_opt = (option->flags & ARG_OPTIONAL) != 0;

if (option->arg == nullptr)
{
err_code = m_argp->parse(key, nullptr, this);
if (err_code != 0) goto error;
continue;
}

if (pos != std::string::npos)
{
err_code = m_argp->parse(key, arg.c_str(), this);
if (err_code != 0) goto error;
continue;
}

if (is_opt)
{
err_code = m_argp->parse(key, nullptr, this);
if (err_code != 0) goto error;
continue;
}

if (idx + 1 != argc)
{
err_code = m_argp->parse(key, args[++idx], this);
if (err_code != 0) goto error;
continue;
}

err_code = handle_missing(false, args[idx]);
goto error;
}
}

// parse previous arguments if IN_ORDER is not set
for (const auto* const arg : args_free)
{
err_code = m_argp->parse(Key::ARG, arg, this);
if (err_code != 0) goto error;
}

// parse rest argv as normal arguments
for (idx = idx + 1; idx < argc; idx++)
{
err_code = m_argp->parse(Key::ARG, args[idx], this);
if (err_code != 0) goto error;
arg_cnt++;
}

if (arg_cnt == 0) m_argp->parse(Key::NO_ARGS, nullptr, this);

err_code = m_argp->parse(Key::END, nullptr, this);
if (err_code != 0) goto error;

err_code = m_argp->parse(Key::SUCCESS, nullptr, this);
if (err_code != 0) goto error;

return 0;

error:
m_argp->parse(Key::ERROR, nullptr, this);
return err_code;
}

int Parser::handle_unknown(bool shrt, const char* argv)
{
if ((m_flags & NO_ERRS) != 0U)
return m_argp->parse(Key::ERROR, nullptr, this);

static const char* const unknown_fmt[2] = {
"unrecognized option '-%s'\n",
"invalid option -- '%s'\n",
};

failure(this, 1, 0, unknown_fmt[shrt], argv + 1); // NOLINT
see(stderr);

if ((m_flags & NO_EXIT) != 0U) return 1;
exit(1);
}

int Parser::handle_missing(bool shrt, const char* argv)
{
if ((m_flags & NO_ERRS) != 0U)
return m_argp->parse(Key::ERROR, nullptr, this);

static const char* const missing_fmt[2] = {
"option '-%s' requires an argument\n",
"option requires an argument -- '%s'\n",
};

failure(this, 2, 0, missing_fmt[shrt], argv + 1); // NOLINT
see(stderr);

if ((m_flags & NO_EXIT) != 0U) return 2;
exit(2);
}

int Parser::handle_excess(const char* argv)
{
if ((m_flags & NO_ERRS) != 0U)
{
return m_argp->parse(Key::ERROR, nullptr, this);
}

failure(this, 3, 0, "option '%s' doesn't allow an argument\n", argv);
see(stderr);

if ((m_flags & NO_EXIT) != 0U) return 3;
exit(3);
}

std::string Parser::basename(const std::string& name)
{
return name.substr(name.find_first_of('/') + 1);
}

} // namespace poafloc

diff --git a/ source/trie.cpp b/ source/trie.cpp

@@ -1,56 +0,0 @@

#include <algorithm>
#include <cstdint>

#include "poafloc/poafloc.hpp"

namespace poafloc {

bool Parser::trie_t::insert(const std::string& option, int key)
{
trie_t* crnt = this;

if (!is_valid(option)) return false;

for (const char chr : option)
{
if (!crnt->m_terminal) crnt->m_key = key;
crnt->m_count++;

const size_t idx = static_cast<unsigned>(chr) - 'a';
if (!crnt->m_children.at(idx))
crnt->m_children.at(idx) = std::make_unique<trie_t>();

crnt = crnt->m_children.at(idx).get();
}

crnt->m_terminal = true;
crnt->m_key = key;

return true;
}

int Parser::trie_t::get(const std::string& option) const
{
const trie_t* crnt = this;

if (!is_valid(option)) return 0;

for (const char chr : option)
{
const size_t idx = static_cast<unsigned>(chr) - 'a';
if (!crnt->m_children.at(idx)) return 0;

crnt = crnt->m_children.at(idx).get();
}

if (!crnt->m_terminal && crnt->m_count > 1) return 0;
return crnt->m_key;
}

bool Parser::trie_t::is_valid(const std::string& option)
{
return std::all_of(
begin(option), end(option), [](char chr) { return std::islower(chr); });
}

} // namespace poafloc

diff --git a/ test/CMakeLists.txt b/ test/CMakeLists.txt

@@ -12,62 +12,18 @@ if(PROJECT_IS_TOP_LEVEL)

enable_testing()
endif()

# ---- Tests ----

add_executable(poafloc_test source/poafloc_test.cpp)
target_link_libraries(poafloc_test PRIVATE poafloc::poafloc)
target_compile_features(poafloc_test PRIVATE cxx_std_20)

add_test(NAME poafloc_test_empty COMMAND poafloc_test)
set_tests_properties(poafloc_test_empty PROPERTIES PASS_REGULAR_EXPRESSION "init\nnoargs\nend\nsuccess")

add_test(NAME poafloc_test_arg_one COMMAND poafloc_test a1)
set_tests_properties(poafloc_test_arg_one PROPERTIES PASS_REGULAR_EXPRESSION "init\narg:a1\nend\nsuccess")

add_test(NAME poafloc_test_arg_two COMMAND poafloc_test a1 a2)
set_tests_properties(poafloc_test_arg_two PROPERTIES PASS_REGULAR_EXPRESSION "init\narg:a1\narg:a2\nend\nsuccess")

add_test(NAME poafloc_test_short COMMAND poafloc_test -s)
set_tests_properties(poafloc_test_short PROPERTIES PASS_REGULAR_EXPRESSION "init\ns\nnoargs\nend\nsuccess")

add_test(NAME poafloc_test_long COMMAND poafloc_test --long)
set_tests_properties(poafloc_test_long PROPERTIES PASS_REGULAR_EXPRESSION "init\nl\nnoargs\nend\nsuccess")

add_test(NAME poafloc_test_short_arg COMMAND poafloc_test -a a1)
set_tests_properties(poafloc_test_short_arg PROPERTIES PASS_REGULAR_EXPRESSION "init\na:a1\nnoargs\nend\nsuccess")

add_test(NAME poafloc_test_long_arg COMMAND poafloc_test --arg a1)
set_tests_properties(poafloc_test_long_arg PROPERTIES PASS_REGULAR_EXPRESSION "init\na:a1\nnoargs\nend\nsuccess")
find_package(Catch2 REQUIRED)
include(Catch)

add_test(NAME poafloc_test_short_arg_equal COMMAND poafloc_test -aa1)
set_tests_properties(poafloc_test_short_arg_equal PROPERTIES PASS_REGULAR_EXPRESSION "init\na:a1\nnoargs\nend\nsuccess")

add_test(NAME poafloc_test_long_arg_equal COMMAND poafloc_test --arg=a1)
set_tests_properties(poafloc_test_long_arg_equal PROPERTIES PASS_REGULAR_EXPRESSION "init\na:a1\nnoargs\nend\nsuccess")

add_test(NAME poafloc_test_short_arg_without COMMAND poafloc_test -a)
set_tests_properties(poafloc_test_short_arg_without PROPERTIES WILL_FAIL TRUE)

add_test(NAME poafloc_test_long_arg_without COMMAND poafloc_test --arg)
set_tests_properties(poafloc_test_long_arg_without PROPERTIES WILL_FAIL TRUE)

add_test(NAME poafloc_test_short_opt COMMAND poafloc_test -oa1)
set_tests_properties(poafloc_test_short_opt PROPERTIES PASS_REGULAR_EXPRESSION "init\no:a1\nnoargs\nend\nsuccess")

add_test(NAME poafloc_test_long_opt COMMAND poafloc_test --opt=a1)
set_tests_properties(poafloc_test_long_opt PROPERTIES PASS_REGULAR_EXPRESSION "init\no:a1\nnoargs\nend\nsuccess")

add_test(NAME poafloc_test_short_opt_without COMMAND poafloc_test -o)
set_tests_properties(poafloc_test_short_opt_without PROPERTIES PASS_REGULAR_EXPRESSION "init\no:default\nnoargs\nend\nsuccess")

add_test(NAME poafloc_test_long_opt_without COMMAND poafloc_test --opt)
set_tests_properties(poafloc_test_long_opt_without PROPERTIES PASS_REGULAR_EXPRESSION "init\no:default\nnoargs\nend\nsuccess")

add_test(NAME poafloc_test_short_opt_after COMMAND poafloc_test -o a1)
set_tests_properties(poafloc_test_short_opt_after PROPERTIES PASS_REGULAR_EXPRESSION "init\no:default\narg:a1\nend\nsuccess")
# ---- Tests ----

add_test(NAME poafloc_test_long_opt_after COMMAND poafloc_test --opt a1)
set_tests_properties(poafloc_test_long_opt_after PROPERTIES PASS_REGULAR_EXPRESSION "init\no:default\narg:a1\nend\nsuccess")
function(add_test DIR NAME)
add_executable("${NAME}" "source/${DIR}/${NAME}.cpp")
target_link_libraries("${NAME}" PRIVATE based::based)
target_link_libraries("${NAME}" PRIVATE Catch2::Catch2WithMain)
target_compile_features("${NAME}" PRIVATE cxx_std_20)
catch_discover_tests("${NAME}")
endfunction()

# ---- End-of-file commands ----

diff --git a/ test/source/poafloc_test.cpp b/ test/source/poafloc_test.cpp

@@ -1,66 +0,0 @@

#include <iostream>
#include <string>

#include "poafloc/poafloc.hpp"

using namespace poafloc; // NOLINT
//
int parse_opt(int key, const char* arg, Parser* parser)
{
std::ignore = parser;

switch (key)
{
case 's':
std::cout << 's' << std::endl;
break;
case 'l':
std::cout << 'l' << std::endl;
break;
case 'a':
std::cout << "a:" << arg << std::endl;
break;
case 'o':
std::cout << "o:" << (arg != nullptr ? arg : "default") << std::endl;
break;
case ARG:
std::cout << "arg:" << arg << std::endl;
break;
case INIT:
std::cout << "init" << std::endl;
break;
case END:
std::cout << "end" << std::endl;
break;
case SUCCESS:
std::cout << "success" << std::endl;
break;
case ERROR:
std::cout << "error" << std::endl;
break;
case NO_ARGS:
std::cout << "noargs" << std::endl;
break;
default:
break;
}

return 0;
}

// clang-format off
static const option_t options[] = {
{nullptr, 's', nullptr, 0, ""},
{ "long", 'l', nullptr, 0, ""},
{ "arg", 'a', "arg", 0, ""},
{ "opt", 'o', "arg", ARG_OPTIONAL, ""},
{},
};
// clang-format on

static const arg_t argp = {options, parse_opt, "", ""};

int main(int argc, char* argv[])
{
return parse(&argp, argc, argv, 0, nullptr);
}

diff --git a/ vcpkg-configuration.json b/ vcpkg-configuration.json

@@ -0,0 +1,15 @@

{
"default-registry": {
"kind": "git",
"baseline": "566f9496b7e00ee0cc00aca0ab90493d122d148a",
"repository": "https://github.com/microsoft/vcpkg"
},
"registries": [
{
"kind": "git",
"repository": "git://git.dimitrijedobrota.com/vcpkg-registry.git",
"baseline": "cc7113e63d7bb988a70e6b6dd91c5cd1af04bdfb",
"packages": [ "based" ]
}
]
}

diff --git a/ vcpkg.json b/ vcpkg.json

@@ -1,12 +1,22 @@

{
"name": "poafloc",
"version-semver": "1.2.0",
"dependencies": [],
"version-semver": "1.3.0",
"dependencies": [
{
"name": "based",
"version>=": "0.2.0"
}
],
"default-features": [],
"features": {
"test": {
"description": "Dependencies for testing",
"dependencies": []
"dependencies": [
{
"name": "catch2",
"version>=": "3.7.1"
}
]
}
},
"builtin-baseline": "eba7c6a894fce24146af4fdf161fef8e90dd6be3"