Browse Source

Initial commit

master
Riyyi 2 years ago
commit
8a1fb689bd
  1. 38
      .clang-format
  2. 7
      .gitignore
  3. 74
      CMakeLists.txt
  4. 74
      README.org
  5. 1
      compile_commands.json
  6. 39
      src/main.cpp
  7. 281
      src/util/argparser.cpp
  8. 75
      src/util/argparser.h
  9. 18
      test/macro.h
  10. 157
      test/main.cpp

38
.clang-format

@ -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
...

7
.gitignore vendored

@ -0,0 +1,7 @@
# Directories
.cache/
.clangd/
build/
# Files

74
CMakeLists.txt

@ -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
README.org

@ -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

1
compile_commands.json

@ -0,0 +1 @@
build/compile_commands.json

39
src/main.cpp

@ -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;
}

281
src/util/argparser.cpp

@ -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

75
src/util/argparser.h

@ -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

18
test/macro.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
test/main.cpp

@ -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…
Cancel
Save