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