From 2cb3c3df4099297b0a0554bb482e2de04fe86b5c Mon Sep 17 00:00:00 2001 From: sanine Date: Wed, 24 Aug 2022 00:02:17 -0500 Subject: add command-line arguments --- CMakeLists.txt | 4 +- demo/.honey.lua.swo | Bin 20480 -> 0 bytes libs/cargs/.appveyor.yml | 29 + libs/cargs/.clang-format | 38 ++ libs/cargs/.codecov.yml | 2 + libs/cargs/.gitignore | 13 + libs/cargs/.travis.yml | 32 ++ libs/cargs/CMakeLists.txt | 105 ++++ libs/cargs/CONTRIBUTING.md | 30 + libs/cargs/LICENSE.md | 21 + libs/cargs/README.md | 153 +++++ libs/cargs/banner.png | Bin 0 -> 17345 bytes libs/cargs/banner.svg | 36 ++ libs/cargs/cmake/CargsConfig.cmake.in | 5 + libs/cargs/cmake/CreateTestList.cmake | 11 + libs/cargs/cmake/EnableWarnings.cmake | 19 + libs/cargs/demo/CMakeLists.txt | 13 + libs/cargs/demo/main.c | 91 +++ libs/cargs/docs/_config.yml | 6 + libs/cargs/docs/_layouts/default.html | 42 ++ libs/cargs/docs/assets/css/default.css | 127 +++++ libs/cargs/docs/assets/css/github.css | 209 +++++++ libs/cargs/docs/assets/css/monakai.css | 210 +++++++ libs/cargs/docs/assets/css/trac.css | 210 +++++++ libs/cargs/docs/assets/css/vim.css | 1 + libs/cargs/docs/assets/img/favicon.png | Bin 0 -> 1584 bytes libs/cargs/docs/assets/img/favicon.svg | 125 ++++ libs/cargs/docs/build.md | 49 ++ libs/cargs/docs/embed.md | 47 ++ libs/cargs/docs/index.md | 129 +++++ libs/cargs/docs/reference/cag_option_fetch.md | 32 ++ libs/cargs/docs/reference/cag_option_get.md | 26 + libs/cargs/docs/reference/cag_option_get_index.md | 29 + libs/cargs/docs/reference/cag_option_get_value.md | 27 + libs/cargs/docs/reference/cag_option_prepare.md | 30 + libs/cargs/docs/reference/cag_option_print.md | 28 + libs/cargs/docs/reference/index.md | 30 + libs/cargs/include/cargs.h | 162 ++++++ libs/cargs/meson.build | 19 + libs/cargs/src/cargs.c | 437 ++++++++++++++ libs/cargs/test/definitions.h | 9 + libs/cargs/test/main.c | 105 ++++ libs/cargs/test/option_test.c | 665 ++++++++++++++++++++++ src/gl/gl.c | 25 + src/glm/glm.c | 1 + src/main.c | 18 +- src/options/CMakeLists.txt | 5 + src/options/options.c | 56 ++ src/options/options.h | 16 + 49 files changed, 3474 insertions(+), 3 deletions(-) delete mode 100644 demo/.honey.lua.swo create mode 100644 libs/cargs/.appveyor.yml create mode 100644 libs/cargs/.clang-format create mode 100644 libs/cargs/.codecov.yml create mode 100644 libs/cargs/.gitignore create mode 100644 libs/cargs/.travis.yml create mode 100644 libs/cargs/CMakeLists.txt create mode 100644 libs/cargs/CONTRIBUTING.md create mode 100644 libs/cargs/LICENSE.md create mode 100644 libs/cargs/README.md create mode 100755 libs/cargs/banner.png create mode 100755 libs/cargs/banner.svg create mode 100644 libs/cargs/cmake/CargsConfig.cmake.in create mode 100644 libs/cargs/cmake/CreateTestList.cmake create mode 100755 libs/cargs/cmake/EnableWarnings.cmake create mode 100755 libs/cargs/demo/CMakeLists.txt create mode 100755 libs/cargs/demo/main.c create mode 100644 libs/cargs/docs/_config.yml create mode 100644 libs/cargs/docs/_layouts/default.html create mode 100644 libs/cargs/docs/assets/css/default.css create mode 100644 libs/cargs/docs/assets/css/github.css create mode 100644 libs/cargs/docs/assets/css/monakai.css create mode 100644 libs/cargs/docs/assets/css/trac.css create mode 100644 libs/cargs/docs/assets/css/vim.css create mode 100644 libs/cargs/docs/assets/img/favicon.png create mode 100644 libs/cargs/docs/assets/img/favicon.svg create mode 100644 libs/cargs/docs/build.md create mode 100644 libs/cargs/docs/embed.md create mode 100644 libs/cargs/docs/index.md create mode 100755 libs/cargs/docs/reference/cag_option_fetch.md create mode 100755 libs/cargs/docs/reference/cag_option_get.md create mode 100755 libs/cargs/docs/reference/cag_option_get_index.md create mode 100755 libs/cargs/docs/reference/cag_option_get_value.md create mode 100644 libs/cargs/docs/reference/cag_option_prepare.md create mode 100755 libs/cargs/docs/reference/cag_option_print.md create mode 100644 libs/cargs/docs/reference/index.md create mode 100644 libs/cargs/include/cargs.h create mode 100644 libs/cargs/meson.build create mode 100644 libs/cargs/src/cargs.c create mode 100755 libs/cargs/test/definitions.h create mode 100755 libs/cargs/test/main.c create mode 100755 libs/cargs/test/option_test.c create mode 100644 src/options/CMakeLists.txt create mode 100644 src/options/options.c create mode 100644 src/options/options.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ea8fca3..b428197 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,12 +32,13 @@ add_subdirectory(${LIB_ROOT}/lua-5.1.5) add_subdirectory(${LIB_ROOT}/honeysuckle) add_subdirectory(${LIB_ROOT}/cglm) add_subdirectory(${LIB_ROOT}/glfw-3.3.8) +add_subdirectory(${LIB_ROOT}/cargs) set(HONEY_SOURCE ${SRC_ROOT}/main.c) add_executable(honey ${HONEY_SOURCE}) -set(LIBRARIES lua5.1 honeysuckle glfw) +set(LIBRARIES lua5.1 honeysuckle glfw cargs) if (WIN32) set(LIBRARIES ${LIBRARIES} opengl32) else() @@ -64,3 +65,4 @@ add_subdirectory(${SRC_ROOT}/image) add_subdirectory(${SRC_ROOT}/util) add_subdirectory(${SRC_ROOT}/test) add_subdirectory(${SRC_ROOT}/glm) +add_subdirectory(${SRC_ROOT}/options) diff --git a/demo/.honey.lua.swo b/demo/.honey.lua.swo deleted file mode 100644 index 73c5d1d..0000000 Binary files a/demo/.honey.lua.swo and /dev/null differ diff --git a/libs/cargs/.appveyor.yml b/libs/cargs/.appveyor.yml new file mode 100644 index 0000000..fa64d2d --- /dev/null +++ b/libs/cargs/.appveyor.yml @@ -0,0 +1,29 @@ +version: 0.1.0.{build} +image: Visual Studio 2017 +configuration: Release + +platform: + - Win32 + - x64 + +clone_folder: c:\projects\cargs + +init: + - cmd: set arch_str= + - cmd: if "%PLATFORM%"=="x64" (set arch_str= Win64) + - cmd: echo %PLATFORM% + - cmd: echo %APPVEYOR_BUILD_WORKER_IMAGE% + - cmd: if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( set GENERATOR="Visual Studio 15 2017%arch_str%" ) + - cmd: if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" ( set GENERATOR="Visual Studio 14 2015%arch_str%" ) + - cmd: if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" ( set GENERATOR="Visual Studio 12 2013%arch_str%" ) + - cmd: echo %GENERATOR% + +before_build: + - cmd: mkdir build.win && cd build.win && cmake --version && cmake .. -G %GENERATOR% -DENABLE_TESTS=1 + +build: + project: c:\projects\cargs\build.win\cargs.sln + verbosity: minimal + +test_script: + - cmd: c:\projects\cargs\build.win\Release\cargstest.exe diff --git a/libs/cargs/.clang-format b/libs/cargs/.clang-format new file mode 100644 index 0000000..eec6156 --- /dev/null +++ b/libs/cargs/.clang-format @@ -0,0 +1,38 @@ +--- +BasedOnStyle: LLVM +AlignAfterOpenBracket: DontAlign +BinPackArguments: true +BinPackParameters: true +BreakBeforeBraces: Custom +IndentWidth: 2 +ContinuationIndentWidth: 2 +AllowAllParametersOfDeclarationOnNextLine: false +AllowAllArgumentsOnNextLine: false +AllowShortBlocksOnASingleLine: 'Never' +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: 'None' +AllowShortIfStatementsOnASingleLine: 'false' +AllowShortLoopsOnASingleLine: false +PenaltyBreakAssignment: 10000 +PenaltyBreakBeforeFirstCallParameter: 100000 +IndentExternBlock: false +BraceWrapping: + AfterExternBlock: false + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: Never + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: false + SplitEmptyNamespace: true diff --git a/libs/cargs/.codecov.yml b/libs/cargs/.codecov.yml new file mode 100644 index 0000000..2515756 --- /dev/null +++ b/libs/cargs/.codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "test" \ No newline at end of file diff --git a/libs/cargs/.gitignore b/libs/cargs/.gitignore new file mode 100644 index 0000000..2b8f3c4 --- /dev/null +++ b/libs/cargs/.gitignore @@ -0,0 +1,13 @@ +test/tests.h +.idea +cmake-build-debug +cmake-build-release +build +build.win +builddir +.vs +.vscode +docs/_site +docs/.jekyll-cache +demo/build +demo/build.win diff --git a/libs/cargs/.travis.yml b/libs/cargs/.travis.yml new file mode 100644 index 0000000..31f1511 --- /dev/null +++ b/libs/cargs/.travis.yml @@ -0,0 +1,32 @@ +language: c + +os: +- linux +- osx +- freebsd + +dist: focal + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-7 + - cmake + +compiler: +- gcc +- clang + +before_script: +- mkdir build +- cd build +- cmake -DENABLE_TESTS=1 -DENABLE_COVERAGE=1 .. + +script: + - make + - make test + +after_success: +- bash <(curl -s https://codecov.io/bash) diff --git a/libs/cargs/CMakeLists.txt b/libs/cargs/CMakeLists.txt new file mode 100644 index 0000000..4cd618f --- /dev/null +++ b/libs/cargs/CMakeLists.txt @@ -0,0 +1,105 @@ +cmake_minimum_required(VERSION 3.14.7) + +# set project name +project(cargs + VERSION 1.0.3 + DESCRIPTION "A simple argument parser library" + LANGUAGES C) + +# include utilities +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +include(EnableWarnings) +include(CTest) +include(CreateTestList) +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +# configure requirements +set(CMAKE_C_STANDARD 11) + +# setup target and directory names +set(LIBRARY_TARGET "cargs") +set(TEST_TARGET "cargstest") +set(INCLUDE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include") +set(SOURCE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src") +set(TEST_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test") + +# enable coverage if requested +if(ENABLE_COVERAGE) + message("-- Coverage enabled") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") +endif() + +# enable sanitizer +if(ENABLE_SANITIZER) + message("-- Sanitizer enabled") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=${ENABLE_SANITIZER}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=${ENABLE_SANITIZER}") +endif() + +# add the main library +add_library(${LIBRARY_TARGET} + "${INCLUDE_DIRECTORY}/cargs.h" + "${SOURCE_DIRECTORY}/cargs.c") +enable_warnings(${LIBRARY_TARGET}) +target_include_directories(${LIBRARY_TARGET} PUBLIC + $ + $ +) +set_target_properties(cargs PROPERTIES PUBLIC_HEADER "${INCLUDE_DIRECTORY}/cargs.h") +set_target_properties(cargs PROPERTIES DEFINE_SYMBOL CAG_EXPORTS) + +# add shared library macro +if(BUILD_SHARED_LIBS) + target_compile_definitions(cargs PUBLIC CAG_SHARED) +endif() + +# add tests +if(ENABLE_TESTS) + message("-- Tests enabled") + enable_testing() + create_test(DEFAULT option complex) + create_test(DEFAULT option mixed) + create_test(DEFAULT option ending) + create_test(DEFAULT option long_missing_value) + create_test(DEFAULT option short_missing_value) + create_test(DEFAULT option long_space_value) + create_test(DEFAULT option short_space_value) + create_test(DEFAULT option long_equal_value) + create_test(DEFAULT option short_equal_value) + create_test(DEFAULT option combined) + create_test(DEFAULT option unknown_long) + create_test(DEFAULT option unknown_short) + create_test(DEFAULT option alias) + create_test(DEFAULT option simple_long) + create_test(DEFAULT option simple) + create_test(DEFAULT option print) + create_test_list(DEFAULT "${TEST_DIRECTORY}/tests.h") + + add_executable(${TEST_TARGET} + "${TEST_DIRECTORY}/main.c" + "${TEST_DIRECTORY}/option_test.c") + target_link_libraries(${TEST_TARGET} PUBLIC ${LIBRARY_TARGET}) + target_include_directories(${TEST_TARGET} PUBLIC "${INCLUDE_DIRECTORY}") + enable_warnings(${TEST_TARGET}) +endif() + +# version file +configure_package_config_file("cmake/CargsConfig.cmake.in" + ${CMAKE_CURRENT_BINARY_DIR}/CargsConfig.cmake + INSTALL_DESTINATION ${LIB_INSTALL_DIR}/cargs/cmake) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/CargsConfigVersion.cmake + COMPATIBILITY SameMajorVersion) + +# installing +install(TARGETS cargs + EXPORT CargsTargets) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/CargsConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/CargsConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cargs) +install(EXPORT CargsTargets + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cargs) diff --git a/libs/cargs/CONTRIBUTING.md b/libs/cargs/CONTRIBUTING.md new file mode 100644 index 0000000..9a5c1ab --- /dev/null +++ b/libs/cargs/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# Contributing +All your contributions to **cargs** are very welcome! **cargs** is especially happy to +receive contributions like: + + * general **bugfixes** + * simple **bug reports** + * **proposing new features** + * **questions** (there are no dumb questions) + +## License +**Any contributions you make will be under the MIT Software License.** + +In short, when you submit code changes, your submissions are understood to be +under the same MIT License that covers the project. Feel free to contact the +maintainers if that's a concern. + +## How to report a bug? +You can just use the issue tracker to do so. + +## How to propose a new feature? +You can just use the issue tracker to do so. + +## How to submit a bug fix? +Just submit a pull-request! Try to make sure that the code style fits the +surrounding code. + +## How to submit a new feature? +You probably want to create an issue first to discuss the change. All +pull-requests will be considered though! Just try to make sure that the code +style fits the surrounding code. \ No newline at end of file diff --git a/libs/cargs/LICENSE.md b/libs/cargs/LICENSE.md new file mode 100644 index 0000000..23f2eba --- /dev/null +++ b/libs/cargs/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Leonard Iklé + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/cargs/README.md b/libs/cargs/README.md new file mode 100644 index 0000000..e1a4260 --- /dev/null +++ b/libs/cargs/README.md @@ -0,0 +1,153 @@ + + +[![Travis Build](https://img.shields.io/travis/com/likle/cargs/master?label=Linux%2C%20macOS%20%26%20FreeBSD)](https://app.travis-ci.com/github/likle/cargs) +[![Appveyor Build](https://img.shields.io/appveyor/ci/likle/cargs/master.svg?label=Windows)](https://ci.appveyor.com/project/likle/cargs) +[![codecov](https://img.shields.io/codecov/c/github/likle/cargs/master.svg?label=Coverage)](https://codecov.io/gh/likle/cargs) +[![Language Grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/likle/cargs.svg?label=Code%20Quality)](https://lgtm.com/projects/g/likle/cargs/context:cpp) + +# libcargs - command line argument library for C/C++ +This is a lighweight C command line argument library. It is currently compiled and +tested under **Windows**, **MacOS** and **Linux**. + +## Features +Please have a look at the +**[reference](https://likle.github.io/cargs/reference/)** for detailed +information. Some features this library includes: + + * **cross-platform** on windows, linux and macOS + * **simple interface** - just one header + * **one simple loop** - to iterate over the arguments + * **automatic help output** - showing all options available + * **long and short options** - giving users alternatives + * **option values** - for options which are more than just flags + + ## Building + **[Building](https://likle.github.io/cargs/build.html)**, + **[embedding](https://likle.github.io/cargs/embed.html)** and + **[testing](https://likle.github.io/cargs/build.html)** instructions are + available in the documentation (it's very easy). + + ## Docs + All the documentation is available in the + **[the github page](https://likle.github.io/cargs/)** of this repository. + + ## Example + ```c +#include +#include +#include + +/** + * This is the main configuration of all options available. + */ +static struct cag_option options[] = { + {.identifier = 's', + .access_letters = "s", + .access_name = NULL, + .value_name = NULL, + .description = "Simple flag"}, + + {.identifier = 'm', + .access_letters = "mMoO", + .access_name = NULL, + .value_name = NULL, + .description = "Multiple access letters"}, + + {.identifier = 'l', + .access_letters = NULL, + .access_name = "long", + .value_name = NULL, + .description = "Long parameter name"}, + + {.identifier = 'k', + .access_letters = "k", + .access_name = "key", + .value_name = "VALUE", + .description = "Parameter value"}, + + {.identifier = 'h', + .access_letters = "h", + .access_name = "help", + .description = "Shows the command help"}}; + +/** + * This is a custom project configuration structure where you can store the + * parsed information. + */ +struct demo_configuration +{ + bool simple_flag; + bool multiple_flag; + bool long_flag; + const char *key; +}; + +int main(int argc, char *argv[]) +{ + char identifier; + const char *value; + cag_option_context context; + struct demo_configuration config = {false, false, false, NULL}; + + /** + * Now we just prepare the context and iterate over all options. Simple! + */ + cag_option_prepare(&context, options, CAG_ARRAY_SIZE(options), argc, argv); + while (cag_option_fetch(&context)) { + identifier = cag_option_get(&context); + switch (identifier) { + case 's': + config.simple_flag = true; + break; + case 'm': + config.multiple_flag = true; + break; + case 'l': + config.long_flag = true; + break; + case 'k': + value = cag_option_get_value(&context); + config.key = value; + break; + case 'h': + printf("Usage: cargsdemo [OPTION]...\n"); + printf("Demonstrates the cargs library.\n\n"); + cag_option_print(options, CAG_ARRAY_SIZE(options), stdout); + printf("\nNote that all formatting is done by cargs.\n"); + return EXIT_SUCCESS; + } + } + + printf("simple_flag: %i, multiple_flag: %i, long_flag: %i, key: %s\n", + config.simple_flag, config.multiple_flag, config.long_flag, + config.key ? config.key : "-"); + + return EXIT_SUCCESS; +} + +``` + +### Example output +```console +foo@bar:~$ ./cargsdemo +simple_flag: 0, multiple_flag: 0, long_flag: 0, key: - +``` + +```console +foo@bar:~$ ./cargsdemo -k=test -sm --long +simple_flag: 1, multiple_flag: 1, long_flag: 1, key: test +``` + +```console +foo@bar:~$ ./cargsdemo --help +Usage: cargsdemo [OPTION]... +Demonstrates the cargs library. + + -s Simple flag + -m, -M, -o, -O Multiple access letters + --long Long parameter name + -k, --key=VALUE Parameter value + -h, --help Shows the command help + +Note that all formatting is done by cargs. +``` diff --git a/libs/cargs/banner.png b/libs/cargs/banner.png new file mode 100755 index 0000000..7de0493 Binary files /dev/null and b/libs/cargs/banner.png differ diff --git a/libs/cargs/banner.svg b/libs/cargs/banner.svg new file mode 100755 index 0000000..404e46a --- /dev/null +++ b/libs/cargs/banner.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + c + args + + + + + diff --git a/libs/cargs/cmake/CargsConfig.cmake.in b/libs/cargs/cmake/CargsConfig.cmake.in new file mode 100644 index 0000000..bb11824 --- /dev/null +++ b/libs/cargs/cmake/CargsConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/CargsTargets.cmake") + +check_required_components(cargs) diff --git a/libs/cargs/cmake/CreateTestList.cmake b/libs/cargs/cmake/CreateTestList.cmake new file mode 100644 index 0000000..2dd46b3 --- /dev/null +++ b/libs/cargs/cmake/CreateTestList.cmake @@ -0,0 +1,11 @@ +function(create_test_list list_name file) + set("TEST_LIST_FILE_${list_name}" ${file} PARENT_SCOPE) + file(WRITE ${file} "#define UNIT_TESTS(XX) \\\n") + file(APPEND ${file} ${TEST_LIST_CONTENT_${list_name}}) + file(APPEND ${file} "\n") +endfunction() + +function(create_test list_name unit_name test_name) + set(TEST_LIST_CONTENT_${list_name} "${TEST_LIST_CONTENT_${list_name}} XX(${unit_name},${test_name}) \\\n" PARENT_SCOPE) + add_test(NAME "${unit_name}_${test_name}" COMMAND ${TEST_TARGET} ${unit_name} ${test_name}) +endfunction() \ No newline at end of file diff --git a/libs/cargs/cmake/EnableWarnings.cmake b/libs/cargs/cmake/EnableWarnings.cmake new file mode 100755 index 0000000..bfce52f --- /dev/null +++ b/libs/cargs/cmake/EnableWarnings.cmake @@ -0,0 +1,19 @@ +# enable warnings +function(enable_warnings target) + if(MSVC) + target_compile_definitions(${target} PRIVATE _CRT_SECURE_NO_WARNINGS) + target_compile_options(${target} PRIVATE /W4) + target_compile_options(${target} PRIVATE /WX) + elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + target_compile_options(${target} PRIVATE -Werror) + target_compile_options(${target} PRIVATE -Wall) + target_compile_options(${target} PRIVATE -Wextra) + target_compile_options(${target} PRIVATE -Wpedantic) + target_compile_options(${target} PRIVATE -Wno-gnu-zero-variadic-macro-arguments) + elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${target} PRIVATE -Wall) + target_compile_options(${target} PRIVATE -Werror) + target_compile_options(${target} PRIVATE -Wextra) + target_compile_options(${target} PRIVATE -Wpedantic) + endif() +endfunction() \ No newline at end of file diff --git a/libs/cargs/demo/CMakeLists.txt b/libs/cargs/demo/CMakeLists.txt new file mode 100755 index 0000000..0d37d45 --- /dev/null +++ b/libs/cargs/demo/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.16) +project(cargsdemo) + +include(FetchContent) +FetchContent_Declare(cargs + GIT_REPOSITORY git@github.com:likle/cargs.git + GIT_TAG stable +) +FetchContent_MakeAvailable(cargs) + +add_executable(cargsdemo main.c) + +target_link_libraries(cargsdemo cargs) diff --git a/libs/cargs/demo/main.c b/libs/cargs/demo/main.c new file mode 100755 index 0000000..8d5167e --- /dev/null +++ b/libs/cargs/demo/main.c @@ -0,0 +1,91 @@ +#include +#include +#include + +/** + * This is the main configuration of all options available. + */ +static struct cag_option options[] = { + {.identifier = 's', + .access_letters = "s", + .access_name = NULL, + .value_name = NULL, + .description = "Simple flag"}, + + {.identifier = 'm', + .access_letters = "mMoO", + .access_name = NULL, + .value_name = NULL, + .description = "Multiple access letters"}, + + {.identifier = 'l', + .access_letters = NULL, + .access_name = "long", + .value_name = NULL, + .description = "Long parameter name"}, + + {.identifier = 'k', + .access_letters = "k", + .access_name = "key", + .value_name = "VALUE", + .description = "Parameter value"}, + + {.identifier = 'h', + .access_letters = "h", + .access_name = "help", + .description = "Shows the command help"}}; + +/** + * This is a custom project configuration structure where you can store the + * parsed information. + */ +struct demo_configuration +{ + bool simple_flag; + bool multiple_flag; + bool long_flag; + const char *key; +}; + +int main(int argc, char *argv[]) +{ + char identifier; + const char *value; + cag_option_context context; + struct demo_configuration config = {false, false, false, NULL}; + + /** + * Now we just prepare the context and iterate over all options. Simple! + */ + cag_option_prepare(&context, options, CAG_ARRAY_SIZE(options), argc, argv); + while (cag_option_fetch(&context)) { + identifier = cag_option_get(&context); + switch (identifier) { + case 's': + config.simple_flag = true; + break; + case 'm': + config.multiple_flag = true; + break; + case 'l': + config.long_flag = true; + break; + case 'k': + value = cag_option_get_value(&context); + config.key = value; + break; + case 'h': + printf("Usage: cargsdemo [OPTION]...\n"); + printf("Demonstrates the cargs library.\n\n"); + cag_option_print(options, CAG_ARRAY_SIZE(options), stdout); + printf("\nNote that all formatting is done by cargs.\n"); + return EXIT_SUCCESS; + } + } + + printf("simple_flag: %i, multiple_flag: %i, long_flag: %i, key: %s\n", + config.simple_flag, config.multiple_flag, config.long_flag, + config.key ? config.key : "-"); + + return EXIT_SUCCESS; +} diff --git a/libs/cargs/docs/_config.yml b/libs/cargs/docs/_config.yml new file mode 100644 index 0000000..88f7fa8 --- /dev/null +++ b/libs/cargs/docs/_config.yml @@ -0,0 +1,6 @@ +defaults: + - + scope: + path: "" # an empty string here means all files in the project + values: + layout: "default" \ No newline at end of file diff --git a/libs/cargs/docs/_layouts/default.html b/libs/cargs/docs/_layouts/default.html new file mode 100644 index 0000000..e326afd --- /dev/null +++ b/libs/cargs/docs/_layouts/default.html @@ -0,0 +1,42 @@ + + + + + {{ page.title }} - cargs + + + + + + + + + + +
+
+ + +
+
+
+
+

{{ page.title }}

+ {{ content }} +
+
+ +
+ find this repository on github +
+ diff --git a/libs/cargs/docs/assets/css/default.css b/libs/cargs/docs/assets/css/default.css new file mode 100644 index 0000000..9389ca0 --- /dev/null +++ b/libs/cargs/docs/assets/css/default.css @@ -0,0 +1,127 @@ +html, +body { + height: 100%; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + margin: auto; + padding: 20px; + padding: 0; + display: flex; + flex-flow: column; + background: #fafafa; + color: #22162B; +} + +header { + text-align: right; + margin-bottom: 20px; + padding: 20px 20px; + font-family: 'Ubuntu Mono'; + background: #451F55; + color: #F8C630; + border-bottom: 10px solid #724E91; +} + +header .inner { + max-width: 800px; + margin: auto; +} + +header .logo { + float: left; + vertical-align: middle; + display: inline-block; + font-weight: bold; + font-size: 32pt; + padding: 0; + margin-left: 10px; + line-height: 50px; +} + +header nav { + line-height: 50px; +} + +header a { + padding: 10px; + text-decoration: none; + font-weight: bold; + font-size: 18pt; + overflow: auto; + color: #E54F6D;; +} + +.main { + flex: 1; +} + +footer { + text-align: center; + margin-top: 50px; + padding: 40px 20px; + font-family: 'Ubuntu Mono'; + background: #22162B; + color: #F8C630; +} + + +a { + color: #E54F6D; +} + +h1, h2 { + font-family: 'Ubuntu Mono'; + padding-bottom: 5px; + border-bottom: 1px solid rgba(255,255,255,.2); +} + +.content { + color: #49483e; + padding-top: 20px; + max-width: 800px; + margin: auto; +} + +.highlight { + padding: 10px; + margin: 0; + overflow: auto; + white-space: pre-wrap; +} + +.highlighter-rouge { + background: rgba(0,0,0,0.1); +} + +table { + border-collapse: collapse; +} + +table td, +table th { + border: 1px solid #DDDDDD; + background: #ffffff; + padding: 5px 8px; +} + +table tr:nth-child(even) td { + background: #f8f9fb; +} + +@media (max-width: 1000px) { + header { + text-align: center; + } + + header .logo { + display: block; + float: none; + margin-bottom: 10px; + } + + .content { + padding: 0 20px; + } +} diff --git a/libs/cargs/docs/assets/css/github.css b/libs/cargs/docs/assets/css/github.css new file mode 100644 index 0000000..daf76ad --- /dev/null +++ b/libs/cargs/docs/assets/css/github.css @@ -0,0 +1,209 @@ +.highlight table td { padding: 5px; } +.highlight table pre { margin: 0; } +.highlight .cm { + color: #999988; + font-style: italic; +} +.highlight .cp { + color: #999999; + font-weight: bold; +} +.highlight .c1 { + color: #999988; + font-style: italic; +} +.highlight .cs { + color: #999999; + font-weight: bold; + font-style: italic; +} +.highlight .c, .highlight .cd { + color: #999988; + font-style: italic; +} +.highlight .err { + color: #a61717; + background-color: #e3d2d2; +} +.highlight .gd { + color: #000000; + background-color: #ffdddd; +} +.highlight .ge { + color: #000000; + font-style: italic; +} +.highlight .gr { + color: #aa0000; +} +.highlight .gh { + color: #999999; +} +.highlight .gi { + color: #000000; + background-color: #ddffdd; +} +.highlight .go { + color: #888888; +} +.highlight .gp { + color: #555555; +} +.highlight .gs { + font-weight: bold; +} +.highlight .gu { + color: #aaaaaa; +} +.highlight .gt { + color: #aa0000; +} +.highlight .kc { + color: #000000; + font-weight: bold; +} +.highlight .kd { + color: #000000; + font-weight: bold; +} +.highlight .kn { + color: #000000; + font-weight: bold; +} +.highlight .kp { + color: #000000; + font-weight: bold; +} +.highlight .kr { + color: #000000; + font-weight: bold; +} +.highlight .kt { + color: #445588; + font-weight: bold; +} +.highlight .k, .highlight .kv { + color: #000000; + font-weight: bold; +} +.highlight .mf { + color: #009999; +} +.highlight .mh { + color: #009999; +} +.highlight .il { + color: #009999; +} +.highlight .mi { + color: #009999; +} +.highlight .mo { + color: #009999; +} +.highlight .m, .highlight .mb, .highlight .mx { + color: #009999; +} +.highlight .sb { + color: #d14; +} +.highlight .sc { + color: #d14; +} +.highlight .sd { + color: #d14; +} +.highlight .s2 { + color: #d14; +} +.highlight .se { + color: #d14; +} +.highlight .sh { + color: #d14; +} +.highlight .si { + color: #d14; +} +.highlight .sx { + color: #d14; +} +.highlight .sr { + color: #009926; +} +.highlight .s1 { + color: #d14; +} +.highlight .ss { + color: #990073; +} +.highlight .s { + color: #d14; +} +.highlight .na { + color: #008080; +} +.highlight .bp { + color: #999999; +} +.highlight .nb { + color: #0086B3; +} +.highlight .nc { + color: #445588; + font-weight: bold; +} +.highlight .no { + color: #008080; +} +.highlight .nd { + color: #3c5d5d; + font-weight: bold; +} +.highlight .ni { + color: #800080; +} +.highlight .ne { + color: #990000; + font-weight: bold; +} +.highlight .nf { + color: #990000; + font-weight: bold; +} +.highlight .nl { + color: #990000; + font-weight: bold; +} +.highlight .nn { + color: #555555; +} +.highlight .nt { + color: #000080; +} +.highlight .vc { + color: #008080; +} +.highlight .vg { + color: #008080; +} +.highlight .vi { + color: #008080; +} +.highlight .nv { + color: #008080; +} +.highlight .ow { + color: #000000; + font-weight: bold; +} +.highlight .o { + color: #000000; + font-weight: bold; +} +.highlight .w { + color: #bbbbbb; +} +.highlight { + background-color: #f8f8f8; +} diff --git a/libs/cargs/docs/assets/css/monakai.css b/libs/cargs/docs/assets/css/monakai.css new file mode 100644 index 0000000..667b3ec --- /dev/null +++ b/libs/cargs/docs/assets/css/monakai.css @@ -0,0 +1,210 @@ +.highlight table td { padding: 5px; } +.highlight table pre { margin: 0; } +.highlight .c, .highlight .cd { + color: #75715e; + font-style: italic; +} +.highlight .cm { + color: #75715e; + font-style: italic; +} +.highlight .c1 { + color: #75715e; + font-style: italic; +} +.highlight .cp { + color: #75715e; + font-weight: bold; +} +.highlight .cs { + color: #75715e; + font-weight: bold; + font-style: italic; +} +.highlight .err { + color: #960050; + background-color: #1e0010; +} +.highlight .gi { + color: #ffffff; + background-color: #324932; +} +.highlight .gd { + color: #ffffff; + background-color: #493131; +} +.highlight .ge { + color: #000000; + font-style: italic; +} +.highlight .gr { + color: #aa0000; +} +.highlight .gt { + color: #aa0000; +} +.highlight .gh { + color: #999999; +} +.highlight .go { + color: #888888; +} +.highlight .gp { + color: #ee7733; +} +.highlight .gs { + font-weight: bold; +} +.highlight .gu { + color: #aaaaaa; +} +.highlight .k, .highlight .kv { + color: #66d9ef; + font-weight: bold; +} +.highlight .kc { + color: #66d9ef; + font-weight: bold; +} +.highlight .kd { + color: #66d9ef; + font-weight: bold; +} +.highlight .kp { + color: #66d9ef; + font-weight: bold; +} +.highlight .kr { + color: #66d9ef; + font-weight: bold; +} +.highlight .kt { + color: #66d9ef; + font-weight: bold; +} +.highlight .kn { + color: #f92672; + font-weight: bold; +} +.highlight .ow { + color: #f92672; + font-weight: bold; +} +.highlight .o { + color: #f92672; + font-weight: bold; +} +.highlight .mf { + color: #ae81ff; +} +.highlight .mh { + color: #ae81ff; +} +.highlight .il { + color: #ae81ff; +} +.highlight .mi { + color: #ae81ff; +} +.highlight .mo { + color: #ae81ff; +} +.highlight .m, .highlight .mb, .highlight .mx { + color: #ae81ff; +} +.highlight .se { + color: #ae81ff; +} +.highlight .sb { + color: #e6db74; +} +.highlight .sc { + color: #e6db74; +} +.highlight .sd { + color: #e6db74; +} +.highlight .s2 { + color: #e6db74; +} +.highlight .sh { + color: #e6db74; +} +.highlight .si { + color: #e6db74; +} +.highlight .sx { + color: #e6db74; +} +.highlight .sr { + color: #e6db74; +} +.highlight .s1 { + color: #e6db74; +} +.highlight .ss { + color: #e6db74; +} +.highlight .s { + color: #e6db74; +} +.highlight .na { + color: #a6e22e; +} +.highlight .nc { + color: #a6e22e; + font-weight: bold; +} +.highlight .nd { + color: #a6e22e; + font-weight: bold; +} +.highlight .ne { + color: #a6e22e; + font-weight: bold; +} +.highlight .nf { + color: #a6e22e; + font-weight: bold; +} +.highlight .no { + color: #66d9ef; +} +.highlight .bp { + color: #f8f8f2; +} +.highlight .nb { + color: #f8f8f2; +} +.highlight .ni { + color: #f8f8f2; +} +.highlight .nn { + color: #f8f8f2; +} +.highlight .vc { + color: #f8f8f2; +} +.highlight .vg { + color: #f8f8f2; +} +.highlight .vi { + color: #f8f8f2; +} +.highlight .nv { + color: #f8f8f2; +} +.highlight .w { + color: #f8f8f2; +} +.highlight .nl { + color: #f8f8f2; + font-weight: bold; +} +.highlight .nt { + color: #f92672; +} +.highlight { + color: #f8f8f2; + background-color: #49483e; +} diff --git a/libs/cargs/docs/assets/css/trac.css b/libs/cargs/docs/assets/css/trac.css new file mode 100644 index 0000000..854cfb8 --- /dev/null +++ b/libs/cargs/docs/assets/css/trac.css @@ -0,0 +1,210 @@ +.highlight table td { padding: 5px; } +.highlight table pre { margin: 0; } +.highlight .c, .highlight .cd { + color: #75715e; + font-style: italic; +} +.highlight .cm { + color: #75715e; + font-style: italic; +} +.highlight .c1 { + color: #75715e; + font-style: italic; +} +.highlight .cp { + color: #75715e; + font-weight: bold; +} +.highlight .cs { + color: #75715e; + font-weight: bold; + font-style: italic; +} +.highlight .err { + color: #960050; + background-color: #1e0010; +} +.highlight .gi { + color: #ffffff; + background-color: #324932; +} +.highlight .gd { + color: #ffffff; + background-color: #493131; +} +.highlight .ge { + color: #000000; + font-style: italic; +} +.highlight .gr { + color: #aa0000; +} +.highlight .gt { + color: #aa0000; +} +.highlight .gh { + color: #999999; +} +.highlight .go { + color: #888888; +} +.highlight .gp { + color: #555555; +} +.highlight .gs { + font-weight: bold; +} +.highlight .gu { + color: #aaaaaa; +} +.highlight .k, .highlight .kv { + color: #66d9ef; + font-weight: bold; +} +.highlight .kc { + color: #66d9ef; + font-weight: bold; +} +.highlight .kd { + color: #66d9ef; + font-weight: bold; +} +.highlight .kp { + color: #66d9ef; + font-weight: bold; +} +.highlight .kr { + color: #66d9ef; + font-weight: bold; +} +.highlight .kt { + color: #66d9ef; + font-weight: bold; +} +.highlight .kn { + color: #f92672; + font-weight: bold; +} +.highlight .ow { + color: #f92672; + font-weight: bold; +} +.highlight .o { + color: #f92672; + font-weight: bold; +} +.highlight .mf { + color: #ae81ff; +} +.highlight .mh { + color: #ae81ff; +} +.highlight .il { + color: #ae81ff; +} +.highlight .mi { + color: #ae81ff; +} +.highlight .mo { + color: #ae81ff; +} +.highlight .m, .highlight .mb, .highlight .mx { + color: #ae81ff; +} +.highlight .se { + color: #ae81ff; +} +.highlight .sb { + color: #e6db74; +} +.highlight .sc { + color: #e6db74; +} +.highlight .sd { + color: #e6db74; +} +.highlight .s2 { + color: #e6db74; +} +.highlight .sh { + color: #e6db74; +} +.highlight .si { + color: #e6db74; +} +.highlight .sx { + color: #e6db74; +} +.highlight .sr { + color: #e6db74; +} +.highlight .s1 { + color: #e6db74; +} +.highlight .ss { + color: #e6db74; +} +.highlight .s { + color: #e6db74; +} +.highlight .na { + color: #a6e22e; +} +.highlight .nc { + color: #a6e22e; + font-weight: bold; +} +.highlight .nd { + color: #a6e22e; + font-weight: bold; +} +.highlight .ne { + color: #a6e22e; + font-weight: bold; +} +.highlight .nf { + color: #a6e22e; + font-weight: bold; +} +.highlight .no { + color: #66d9ef; +} +.highlight .bp { + color: #f8f8f2; +} +.highlight .nb { + color: #f8f8f2; +} +.highlight .ni { + color: #f8f8f2; +} +.highlight .nn { + color: #f8f8f2; +} +.highlight .vc { + color: #f8f8f2; +} +.highlight .vg { + color: #f8f8f2; +} +.highlight .vi { + color: #f8f8f2; +} +.highlight .nv { + color: #f8f8f2; +} +.highlight .w { + color: #f8f8f2; +} +.highlight .nl { + color: #f8f8f2; + font-weight: bold; +} +.highlight .nt { + color: #f92672; +} +.highlight { + color: #f8f8f2; + background-color: #49483e; +} diff --git a/libs/cargs/docs/assets/css/vim.css b/libs/cargs/docs/assets/css/vim.css new file mode 100644 index 0000000..75e00ef --- /dev/null +++ b/libs/cargs/docs/assets/css/vim.css @@ -0,0 +1 @@ +unknown theme: vim diff --git a/libs/cargs/docs/assets/img/favicon.png b/libs/cargs/docs/assets/img/favicon.png new file mode 100644 index 0000000..b4d46f6 Binary files /dev/null and b/libs/cargs/docs/assets/img/favicon.png differ diff --git a/libs/cargs/docs/assets/img/favicon.svg b/libs/cargs/docs/assets/img/favicon.svg new file mode 100644 index 0000000..acf0df6 --- /dev/null +++ b/libs/cargs/docs/assets/img/favicon.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + C + + diff --git a/libs/cargs/docs/build.md b/libs/cargs/docs/build.md new file mode 100644 index 0000000..599c114 --- /dev/null +++ b/libs/cargs/docs/build.md @@ -0,0 +1,49 @@ +--- +title: Building +description: A guide on how to build the cargs command line arguments parser library for C/C++. +--- + +In order to build the source, you will have to download it. You can do so using git (or download it from [here](https://github.com/likle/cargs/archive/stable.zip)). +```bash +git clone -b stable git@github.com:likle/cargs.git +``` + +**Note**: The *stable* branch points to the latest stable version. You should +always use a stable version in production code. + +## Using Windows +Visual Studio 2017 is recommended, then you can just open the source using ``File -> Open -> CMake...``. You can use Visual Studio to compile the source and debug the code. Make sure you have the CMake and C/C++ features enabled. + +## Using Ubuntu +You will need [CMake](https://cmake.org/download/) and either gcc or clang installed. On Ubuntu you can use the following to compile **cargs**: +```bash +sudo apt-get install build-essential cmake +mkdir cargs/build +cd cargs/build +cmake .. +make +``` + +## Using MacOS +You will need [CMake](https://cmake.org/download/) and either gcc or clang installed. On MacOS you can use the following to compile **cargs**: +``` +brew install cmake gcc +mkdir cargs/build +cd cargs/build +cmake .. +make +``` +# Running Tests +After building **cargs** you can run tests to ensure everything is fine. In order to do that, make sure that you are in the build folder and then execute the test program: + +```bash +./cargstest +``` + +That's it! + +You can even specify which tests to execute by optionally specifying the category and test name: +```bash +# ./cargstest [category] [test] +./cargstest option complex +``` diff --git a/libs/cargs/docs/embed.md b/libs/cargs/docs/embed.md new file mode 100644 index 0000000..ed64d4e --- /dev/null +++ b/libs/cargs/docs/embed.md @@ -0,0 +1,47 @@ +--- +title: Embedding +description: A guide on how to embed the cargs command line parser library for C/C++. +--- + + +In order to embed **cargs**, you will have to download it. +You can do so using git (or download it from [here](https://github.com/likle/cargs/archive/stable.zip)). + +```bash +git clone -b stable git@github.com:likle/cargs.git +``` +**Note**: The *stable* branch points to the latest stable version. You should +always use a stable version in production code. + +## Using CMake to embed cargs +If you are using CMake it is fairly easy to embed **cargs**. +This only requires two lines, you don't even have to specify the include directories. +The following example shows how to do so: +```cmake +# Some basics you will need in your cmake file. +cmake_minimum_required(VERSION 3.9.2) +project(example C) +add_executable(example_target main.c) + +# Replace your_path_to_cargs with the path to your cargs copy. +# This could be something like "${CMAKE_CURRENT_SOURCE_DIR}/lib/cargs". +add_subdirectory(your_path_to_cargs) + +# Replace example_target with the target name which requires cargs. +# After this, there is no need to specify any include directories. +target_link_libraries(example_target cargs) +``` + +After that, you should be able to use cargs in your source code: +```c +#include +``` + +## Directly embed cargs in your source +If you don't use CMake and would like to embed **cargs** directly, you could +just add the two files ``src/cargs.c`` and ``ìnclude/cargs.h`` to your project. +The folder containing ``cargs.h`` has to be in your include directories +([Visual Studio](https://docs.microsoft.com/en-us/cpp/ide/vcpp-directories-property-page?view=vs-2017), +[Eclipse](https://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.cdt.doc.user%2Freference%2Fcdt_u_prop_general_pns_inc.htm), +[gcc](https://www.rapidtables.com/code/linux/gcc/gcc-i.html), +[clang](https://clang.llvm.org/docs/ClangCommandLineReference.html#include-path-management)). diff --git a/libs/cargs/docs/index.md b/libs/cargs/docs/index.md new file mode 100644 index 0000000..60c598e --- /dev/null +++ b/libs/cargs/docs/index.md @@ -0,0 +1,129 @@ +--- +title: C/C++ Command Line Argument Parser +description: cargs is a lightweight C/C++ command line argument parser library which can be used to parse argv and argc parameters passed to a main function. +--- + +## What is cargs? +**cargs** is a lightweight C/C++ command line argument parser library which can be used to parse argv and argc parameters passed to a main function. + +## Example +Here is a simple example of cargs in action. + ```c +#include +#include +#include + +/** + * This is the main configuration of all options available. + */ +static struct cag_option options[] = { + {.identifier = 's', + .access_letters = "s", + .access_name = NULL, + .value_name = NULL, + .description = "Simple flag"}, + + {.identifier = 'm', + .access_letters = "mMoO", + .access_name = NULL, + .value_name = NULL, + .description = "Multiple access letters"}, + + {.identifier = 'l', + .access_letters = NULL, + .access_name = "long", + .value_name = NULL, + .description = "Long parameter name"}, + + {.identifier = 'k', + .access_letters = "k", + .access_name = "key", + .value_name = "VALUE", + .description = "Parameter value"}, + + {.identifier = 'h', + .access_letters = "h", + .access_name = "help", + .description = "Shows the command help"}}; + +/** + * This is a custom project configuration structure where you can store the + * parsed information. + */ +struct demo_configuration +{ + bool simple_flag; + bool multiple_flag; + bool long_flag; + const char *key; +}; + +int main(int argc, char *argv[]) +{ + char identifier; + const char *value; + cag_option_context context; + struct demo_configuration config = {false, false, false, NULL}; + + /** + * Now we just prepare the context and iterate over all options. Simple! + */ + cag_option_prepare(&context, options, CAG_ARRAY_SIZE(options), argc, argv); + while (cag_option_fetch(&context)) { + identifier = cag_option_get(&context); + switch (identifier) { + case 's': + config.simple_flag = true; + break; + case 'm': + config.multiple_flag = true; + break; + case 'l': + config.long_flag = true; + break; + case 'k': + value = cag_option_get_value(&context); + config.key = value; + break; + case 'h': + printf("Usage: cargsdemo [OPTION]...\n"); + printf("Demonstrates the cargs library.\n\n"); + cag_option_print(options, CAG_ARRAY_SIZE(options), stdout); + printf("\nNote that all formatting is done by cargs.\n"); + return EXIT_SUCCESS; + } + } + + printf("simple_flag: %i, multiple_flag: %i, long_flag: %i, key: %s\n", + config.simple_flag, config.multiple_flag, config.long_flag, + config.key ? config.key : "-"); + + return EXIT_SUCCESS; +} + +``` + +### Example output +```console +foo@bar:~$ ./cargsdemo +simple_flag: 0, multiple_flag: 0, long_flag: 0, key: - +``` +
+```console +foo@bar:~$ ./cargsdemo -k=test -sm --long +simple_flag: 1, multiple_flag: 1, long_flag: 1, key: test +``` +
+```console +foo@bar:~$ ./cargsdemo --help +Usage: cargsdemo [OPTION]... +Demonstrates the cargs library. + + -s Simple flag + -m, -M, -o, -O Multiple access letters + --long Long parameter name + -k, --key=VALUE Parameter value + -h, --help Shows the command help + +Note that all formatting is done by cargs. +``` diff --git a/libs/cargs/docs/reference/cag_option_fetch.md b/libs/cargs/docs/reference/cag_option_fetch.md new file mode 100755 index 0000000..331ac20 --- /dev/null +++ b/libs/cargs/docs/reference/cag_option_fetch.md @@ -0,0 +1,32 @@ +--- +title: cag_option_fetch +description: Fetches an option from the argument list. +--- + +_(since v1.0.0)_ +Fetches an option from the argument list. + +## Description +```c +bool cag_option_fetch(cag_option_context *context); +``` + +This function fetches a single option from the argument list. The context +will be moved to that item. Information can be extracted from the context +after the item has been fetched. +The arguments will be re-ordered, which means that non-option arguments will +be moved to the end of the argument list. After all options have been +fetched, all non-option arguments will be positioned after the index of +the context. + +## Parameters + * **context**: The context from which we will fetch the option. + +## Return Value +Returns true if there was another option or false if the end is reached. + +## Changelog + +| Version | Description | +|------------|--------------------------------------------------------| +| **v1.0.0** | The function is introduced. | diff --git a/libs/cargs/docs/reference/cag_option_get.md b/libs/cargs/docs/reference/cag_option_get.md new file mode 100755 index 0000000..8777744 --- /dev/null +++ b/libs/cargs/docs/reference/cag_option_get.md @@ -0,0 +1,26 @@ +--- +title: cag_option_get +description: Gets the identifier of the option. +--- + +_(since v1.0.0)_ +Gets the identifier of the option. + +## Description +```c +char cag_option_get(const cag_option_context *context); +``` +This function gets the identifier of the option, which should be unique to +this option and can be used to determine what kind of option this is. + +## Parameters + * **context**: The context from which the option was fetched. + +## Return Value +Returns the identifier of the option. + +## Changelog + +| Version | Description | +|------------|--------------------------------------------------------| +| **v1.0.0** | The function is introduced. | diff --git a/libs/cargs/docs/reference/cag_option_get_index.md b/libs/cargs/docs/reference/cag_option_get_index.md new file mode 100755 index 0000000..ef27d88 --- /dev/null +++ b/libs/cargs/docs/reference/cag_option_get_index.md @@ -0,0 +1,29 @@ +--- +title: cag_option_get_index +description: Gets the current index of the context. +--- + +_(since v1.0.0)_ +Gets the current index of the context. + +## Description +```c +int cag_option_get_index(const cag_option_context *context); +``` + +This function gets the index within the argv arguments of the context. The +context always points to the next item which it will inspect. This is +particularly useful to inspect the original argument array, or to get +non-option arguments after option fetching has finished. + +## Parameters + * **context**: The context from which the option was fetched. + +## Return Value +Returns the current index of the context. + +## Changelog + +| Version | Description | +|------------|--------------------------------------------------------| +| **v1.0.0** | The function is introduced. | diff --git a/libs/cargs/docs/reference/cag_option_get_value.md b/libs/cargs/docs/reference/cag_option_get_value.md new file mode 100755 index 0000000..462e2e7 --- /dev/null +++ b/libs/cargs/docs/reference/cag_option_get_value.md @@ -0,0 +1,27 @@ +--- +title: cag_option_get_value +description: Gets the value from the option. +--- + +_(since v1.0.0)_ +Gets the value from the option. + +## Description +```c +const char *cag_option_get_value(const cag_option_context *context); +``` + +This function gets the value from the option, if any. If the option does not +contain a value, this function will return NULL. + +## Parameters + * **context**: The context from which the option was fetched. + +## Return Value +Returns a pointer to the value or NULL if there is no value. + +## Changelog + +| Version | Description | +|------------|--------------------------------------------------------| +| **v1.0.0** | The function is introduced. | diff --git a/libs/cargs/docs/reference/cag_option_prepare.md b/libs/cargs/docs/reference/cag_option_prepare.md new file mode 100644 index 0000000..c9aa9e5 --- /dev/null +++ b/libs/cargs/docs/reference/cag_option_prepare.md @@ -0,0 +1,30 @@ +--- +title: cag_option_prepare +description: Prepare argument options context for parsing. +--- + +_(since v1.0.0)_ +Prepare argument options context for parsing. + +## Description +```c +void cag_option_prepare(cag_option_context *context, const cag_option *options, + size_t option_count, int argc, char **argv); +``` + +This function prepares the context for iteration and initializes the context +with the supplied options and arguments. After the context has been prepared, +it can be used to fetch arguments from it. + +## Parameters + * **context**: The context which will be initialized. + * **options**: The registered options which are available for the program. + * **option_count**: The amount of options which are available for the program. + * **argc**: The amount of arguments the user supplied in the main function. + * **argv**: A pointer to the arguments of the main function. + +## Changelog + +| Version | Description | +|------------|--------------------------------------------------------| +| **v1.0.0** | The function is introduced. | diff --git a/libs/cargs/docs/reference/cag_option_print.md b/libs/cargs/docs/reference/cag_option_print.md new file mode 100755 index 0000000..c4531eb --- /dev/null +++ b/libs/cargs/docs/reference/cag_option_print.md @@ -0,0 +1,28 @@ +--- +title: cag_option_print +description: Prints all options to the terminal. +--- + +_(since v1.0.0)_ +description: Prints all options to the terminal. + +## Description +```c +void cag_option_print(const cag_option *options, size_t option_count, + FILE *destination); +``` + +This function prints all options to the terminal. This can be used to generate +the output for a "--help" option. + +## Parameters + * **options**: The options which will be printed. + * **option_count**: The option count which will be printed. + * **destination**: The destination where the output will be printed. + + +## Changelog + +| Version | Description | +|------------|--------------------------------------------------------| +| **v1.0.0** | The function is introduced. | diff --git a/libs/cargs/docs/reference/index.md b/libs/cargs/docs/reference/index.md new file mode 100644 index 0000000..654d9fe --- /dev/null +++ b/libs/cargs/docs/reference/index.md @@ -0,0 +1,30 @@ +--- +title: Reference +description: A complete reference of the cargs command line arguments parser library for C/C++. +--- + +## Basic +The basic functions available in cargs, which can be used to do basic command line argument parsing. + +### Functions +* **[cag_option_prepare]({{ site.baseurl }}{% link reference/cag_option_prepare.md %})** +Prepare argument options context for parsing. + +* **[cag_option_fetch]({{ site.baseurl }}{% link reference/cag_option_fetch.md %})** +Fetches an option from the argument list. + +* **[cag_option_get]({{ site.baseurl }}{% link reference/cag_option_get.md %})** + Gets the identifier of the option. + +* **[cag_option_get_value]({{ site.baseurl }}{% link reference/cag_option_get_value.md %})** +Gets the value from the option. + +* **[cag_option_get_index]({{ site.baseurl }}{% link reference/cag_option_get_index.md %})** +Gets the current index of the context. + +## Output +This section describes functions which are used to output argument information. + +### Functions +* **[cag_option_print]({{ site.baseurl }}{% link reference/cag_option_print.md %})** +Prints all options to the terminal. diff --git a/libs/cargs/include/cargs.h b/libs/cargs/include/cargs.h new file mode 100644 index 0000000..17cba0a --- /dev/null +++ b/libs/cargs/include/cargs.h @@ -0,0 +1,162 @@ +#pragma once + +/** + * This is a simple alternative cross-platform implementation of getopt, which + * is used to parse argument strings submitted to the executable (argc and argv + * which are received in the main function). + */ + +#ifndef CAG_LIBRARY_H +#define CAG_LIBRARY_H + +#include +#include +#include + +#if defined(_WIN32) || defined(__CYGWIN__) +#define CAG_EXPORT __declspec(dllexport) +#define CAG_IMPORT __declspec(dllimport) +#elif __GNUC__ >= 4 +#define CAG_EXPORT __attribute__((visibility("default"))) +#define CAG_IMPORT __attribute__((visibility("default"))) +#else +#define CAG_EXPORT +#define CAG_IMPORT +#endif + +#if defined(CAG_SHARED) +#if defined(CAG_EXPORTS) +#define CAG_PUBLIC CAG_EXPORT +#else +#define CAG_PUBLIC CAG_IMPORT +#endif +#else +#define CAG_PUBLIC +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * An option is used to describe a flag/argument option submitted when the + * program is run. + */ +typedef struct cag_option +{ + const char identifier; + const char *access_letters; + const char *access_name; + const char *value_name; + const char *description; +} cag_option; + +/** + * A context is used to iterate over all options provided. It stores the parsing + * state. + */ +typedef struct cag_option_context +{ + const struct cag_option *options; + size_t option_count; + int argc; + char **argv; + int index; + int inner_index; + bool forced_end; + char identifier; + char *value; +} cag_option_context; + +/** + * This is just a small macro which calculates the size of an array. + */ +#define CAG_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +/** + * @brief Prints all options to the terminal. + * + * This function prints all options to the terminal. This can be used to + * generate the output for a "--help" option. + * + * @param options The options which will be printed. + * @param option_count The option count which will be printed. + * @param destination The destination where the output will be printed. + */ +CAG_PUBLIC void cag_option_print(const cag_option *options, size_t option_count, + FILE *destination); + +/** + * @brief Prepare argument options context for parsing. + * + * This function prepares the context for iteration and initializes the context + * with the supplied options and arguments. After the context has been prepared, + * it can be used to fetch arguments from it. + * + * @param context The context which will be initialized. + * @param options The registered options which are available for the program. + * @param option_count The amount of options which are available for the + * program. + * @param argc The amount of arguments the user supplied in the main function. + * @param argv A pointer to the arguments of the main function. + */ +CAG_PUBLIC void cag_option_prepare(cag_option_context *context, + const cag_option *options, size_t option_count, int argc, char **argv); + +/** + * @brief Fetches an option from the argument list. + * + * This function fetches a single option from the argument list. The context + * will be moved to that item. Information can be extracted from the context + * after the item has been fetched. + * The arguments will be re-ordered, which means that non-option arguments will + * be moved to the end of the argument list. After all options have been + * fetched, all non-option arguments will be positioned after the index of + * the context. + * + * @param context The context from which we will fetch the option. + * @return Returns true if there was another option or false if the end is + * reached. + */ +CAG_PUBLIC bool cag_option_fetch(cag_option_context *context); + +/** + * @brief Gets the identifier of the option. + * + * This function gets the identifier of the option, which should be unique to + * this option and can be used to determine what kind of option this is. + * + * @param context The context from which the option was fetched. + * @return Returns the identifier of the option. + */ +CAG_PUBLIC char cag_option_get(const cag_option_context *context); + +/** + * @brief Gets the value from the option. + * + * This function gets the value from the option, if any. If the option does not + * contain a value, this function will return NULL. + * + * @param context The context from which the option was fetched. + * @return Returns a pointer to the value or NULL if there is no value. + */ +CAG_PUBLIC const char *cag_option_get_value(const cag_option_context *context); + +/** + * @brief Gets the current index of the context. + * + * This function gets the index within the argv arguments of the context. The + * context always points to the next item which it will inspect. This is + * particularly useful to inspect the original argument array, or to get + * non-option arguments after option fetching has finished. + * + * @param context The context from which the option was fetched. + * @return Returns the current index of the context. + */ +CAG_PUBLIC int cag_option_get_index(const cag_option_context *context); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/libs/cargs/meson.build b/libs/cargs/meson.build new file mode 100644 index 0000000..5111af1 --- /dev/null +++ b/libs/cargs/meson.build @@ -0,0 +1,19 @@ +project('cargs', 'c', + license: 'MIT', + meson_version: '>= 0.45.1' +) + +cargs_inc = include_directories('include') + +cargs = library('cargs', 'src/cargs.c', + install: true, + include_directories: cargs_inc +) + +install_headers('include/cargs.h') + +cargs_dep = declare_dependency(include_directories: 'include', link_with: cargs) + +if meson.version().version_compare('>= 0.54.0') + meson.override_dependency('cargs', cargs_dep) +endif diff --git a/libs/cargs/src/cargs.c b/libs/cargs/src/cargs.c new file mode 100644 index 0000000..2d7d945 --- /dev/null +++ b/libs/cargs/src/cargs.c @@ -0,0 +1,437 @@ +#include +#include +#include +#include +#include + +#define CAG_OPTION_PRINT_DISTANCE 4 +#define CAG_OPTION_PRINT_MIN_INDENTION 20 + +static void cag_option_print_value(const cag_option *option, + size_t *accessor_length, FILE *destination) +{ + if (option->value_name != NULL) { + *accessor_length += fprintf(destination, "=%s", option->value_name); + } +} + +static void cag_option_print_letters(const cag_option *option, bool *first, + size_t *accessor_length, FILE *destination) +{ + const char *access_letter; + access_letter = option->access_letters; + if (access_letter != NULL) { + while (*access_letter) { + if (*first) { + *accessor_length += fprintf(destination, "-%c", *access_letter); + *first = false; + } else { + *accessor_length += fprintf(destination, ", -%c", *access_letter); + } + ++access_letter; + } + } +} + +static void cag_option_print_name(const cag_option *option, bool *first, + size_t *accessor_length, FILE *destination) +{ + if (option->access_name != NULL) { + if (*first) { + *accessor_length += fprintf(destination, "--%s", option->access_name); + } else { + *accessor_length += fprintf(destination, ", --%s", option->access_name); + } + } +} + +static size_t cag_option_get_print_indention(const cag_option *options, + size_t option_count) +{ + size_t option_index, indention, result; + const cag_option *option; + + result = CAG_OPTION_PRINT_MIN_INDENTION; + + for (option_index = 0; option_index < option_count; ++option_index) { + indention = CAG_OPTION_PRINT_DISTANCE; + option = &options[option_index]; + if (option->access_letters != NULL && *option->access_letters) { + indention += strlen(option->access_letters) * 4 - 2; + if (option->access_name != NULL) { + indention += strlen(option->access_name) + 4; + } + } else if (option->access_name != NULL) { + indention += strlen(option->access_name) + 2; + } + + if (option->value_name != NULL) { + indention += strlen(option->value_name) + 1; + } + + if (indention > result) { + result = indention; + } + } + + return result; +} + +void cag_option_print(const cag_option *options, size_t option_count, + FILE *destination) +{ + size_t option_index, indention, i, accessor_length; + const cag_option *option; + bool first; + + indention = cag_option_get_print_indention(options, option_count); + + for (option_index = 0; option_index < option_count; ++option_index) { + option = &options[option_index]; + accessor_length = 0; + first = true; + + fputs(" ", destination); + + cag_option_print_letters(option, &first, &accessor_length, destination); + cag_option_print_name(option, &first, &accessor_length, destination); + cag_option_print_value(option, &accessor_length, destination); + + for (i = accessor_length; i < indention; ++i) { + fputs(" ", destination); + } + + fputs(" ", destination); + fputs(option->description, destination); + + fprintf(destination, "\n"); + } +} + +void cag_option_prepare(cag_option_context *context, const cag_option *options, + size_t option_count, int argc, char **argv) +{ + // This just initialized the values to the beginning of all the arguments. + context->options = options; + context->option_count = option_count; + context->argc = argc; + context->argv = argv; + context->index = 1; + context->inner_index = 0; + context->forced_end = false; +} + +static const cag_option *cag_option_find_by_name(cag_option_context *context, + char *name, size_t name_size) +{ + const cag_option *option; + size_t i; + + // We loop over all the available options and stop as soon as we have found + // one. We don't use any hash map table, since there won't be that many + // arguments anyway. + for (i = 0; i < context->option_count; ++i) { + option = &context->options[i]; + + // The option might not have an item name, we can just skip those. + if (option->access_name == NULL) { + continue; + } + + // Try to compare the name of the access name. We can use the name_size or + // this comparison, since we are guaranteed to have null-terminated access + // names. + if (strncmp(option->access_name, name, name_size) == 0) { + return option; + } + } + + return NULL; +} + +static const cag_option *cag_option_find_by_letter(cag_option_context *context, + char letter) +{ + const cag_option *option; + size_t i; + + // We loop over all the available options and stop as soon as we have found + // one. We don't use any look up table, since there won't be that many + // arguments anyway. + for (i = 0; i < context->option_count; ++i) { + option = &context->options[i]; + + // If this option doesn't have any access letters we will skip them. + if (option->access_letters == NULL) { + continue; + } + + // Verify whether this option has the access letter in it's access letter + // string. If it does, then this is our option. + if (strchr(option->access_letters, letter) != NULL) { + return option; + } + } + + return NULL; +} + +static void cag_option_parse_value(cag_option_context *context, + const cag_option *option, char **c) +{ + // And now let's check whether this option is supposed to have a value, which + // is the case if there is a value name set. The value can be either submitted + // with a '=' sign or a space, which means we would have to jump over to the + // next argv index. This is somewhat ugly, but we do it to behave the same as + // the other option parsers. + if (option->value_name != NULL) { + if (**c == '=') { + context->value = ++(*c); + } else { + // If the next index is larger or equal to the argument count, then the + // parameter for this option is missing. The user will know about this, + // since the value pointer of the context will be NULL because we don't + // set it here in that case. + if (context->argc > context->index + 1) { + // We consider this argv to be the value, no matter what the contents + // are. + ++context->index; + *c = context->argv[context->index]; + context->value = *c; + } + } + + // Move c to the end of the value, to not confuse the caller about our + // position. + while (**c) { + ++(*c); + } + } +} + +static void cag_option_parse_access_name(cag_option_context *context, char **c) +{ + const cag_option *option; + char *n; + + // Now we need to extract the access name, which is any symbol up to a '=' or + // a '\0'. + n = *c; + while (**c && **c != '=') { + ++*c; + } + + // Now this will obviously always be true, but we are paranoid. Sometimes. It + // doesn't hurt to check. + assert(*c >= n); + + // Figure out which option this name belongs to. This might return NULL if the + // name is not registered, which means the user supplied an unknown option. In + // that case we return true to indicate that we finished with this option. We + // have to skip the value parsing since we don't know whether the user thinks + // this option has one or not. Since we don't set any identifier specifically, + // it will remain '?' within the context. + option = cag_option_find_by_name(context, n, (size_t)(*c - n)); + if (option == NULL) { + // Since this option is invalid, we will move on to the next index. There is + // nothing we can do about this. + ++context->index; + return; + } + + // We found an option and now we can specify the identifier within the + // context. + context->identifier = option->identifier; + + // And now we try to parse the value. This function will also check whether + // this option is actually supposed to have a value. + cag_option_parse_value(context, option, c); + + // And finally we move on to the next index. + ++context->index; +} + +static void cag_option_parse_access_letter(cag_option_context *context, + char **c) +{ + const cag_option *option; + char *n = *c; + char *v; + + // Figure out which option this letter belongs to. This might return NULL if + // the letter is not registered, which means the user supplied an unknown + // option. In that case we return true to indicate that we finished with this + // option. We have to skip the value parsing since we don't know whether the + // user thinks this option has one or not. Since we don't set any identifier + // specifically, it will remain '?' within the context. + option = cag_option_find_by_letter(context, n[context->inner_index]); + if (option == NULL) { + ++context->index; + context->inner_index = 0; + return; + } + + // We found an option and now we can specify the identifier within the + // context. + context->identifier = option->identifier; + + // And now we try to parse the value. This function will also check whether + // this option is actually supposed to have a value. + v = &n[++context->inner_index]; + cag_option_parse_value(context, option, &v); + + // Check whether we reached the end of this option argument. + if (*v == '\0') { + ++context->index; + context->inner_index = 0; + } +} + +static void cag_option_shift(cag_option_context *context, int start, int option, + int end) +{ + char *tmp; + int a_index, shift_index, shift_count, left_index, right_index; + + shift_count = option - start; + + // There is no shift is required if the start and the option have the same + // index. + if (shift_count == 0) { + return; + } + + // Lets loop through the option strings first, which we will move towards the + // beginning. + for (a_index = option; a_index < end; ++a_index) { + // First remember the current option value, because we will have to save + // that later at the beginning. + tmp = context->argv[a_index]; + + // Let's loop over all option values and shift them one towards the end. + // This will override the option value we just stored temporarily. + for (shift_index = 0; shift_index < shift_count; ++shift_index) { + left_index = a_index - shift_index; + right_index = a_index - shift_index - 1; + context->argv[left_index] = context->argv[right_index]; + } + + // Now restore the saved option value at the beginning. + context->argv[a_index - shift_count] = tmp; + } + + // The new index will be before all non-option values, in such a way that they + // all will be moved again in the next fetch call. + context->index = end - shift_count; +} + +static bool cag_option_is_argument_string(const char *c) +{ + return *c == '-' && *(c + 1) != '\0'; +} + +static int cag_option_find_next(cag_option_context *context) +{ + int next_index, next_option_index; + char *c; + + // Prepare to search the next option at the next index. + next_index = context->index; + next_option_index = next_index; + + // Grab a pointer to the string and verify that it is not the end. If it is + // the end, we have to return false to indicate that we finished. + c = context->argv[next_option_index]; + if (context->forced_end || c == NULL) { + return -1; + } + + // Check whether it is a '-'. We need to find the next option - and an option + // always starts with a '-'. If there is a string "-\0", we don't consider it + // as an option neither. + while (!cag_option_is_argument_string(c)) { + c = context->argv[++next_option_index]; + if (c == NULL) { + // We reached the end and did not find any argument anymore. Let's tell + // our caller that we reached the end. + return -1; + } + } + + // Indicate that we found an option which can be processed. The index of the + // next option will be returned. + return next_option_index; +} + +bool cag_option_fetch(cag_option_context *context) +{ + char *c; + int old_index, new_index; + + // Reset our identifier to a question mark, which indicates an "unknown" + // option. The value is set to NULL, to make sure we are not carrying the + // parameter from the previous option to this one. + context->identifier = '?'; + context->value = NULL; + + // Check whether there are any options left to parse and remember the old + // index as well as the new index. In the end we will move the option junk to + // the beginning, so that non option arguments can be read. + old_index = context->index; + new_index = cag_option_find_next(context); + if (new_index >= 0) { + context->index = new_index; + } else { + return false; + } + + // Grab a pointer to the beginning of the option. At this point, the next + // character must be a '-', since if it was not the prepare function would + // have returned false. We will skip that symbol and proceed. + c = context->argv[context->index]; + assert(*c == '-'); + ++c; + + // Check whether this is a long option, starting with a double "--". + if (*c == '-') { + ++c; + + // This might be a double "--" which indicates the end of options. If this + // is the case, we will not move to the next index. That ensures that + // another call to the fetch function will not skip the "--". + if (*c == '\0') { + context->forced_end = true; + } else { + // We parse now the access name. All information about it will be written + // to the context. + cag_option_parse_access_name(context, &c); + } + } else { + // This is no long option, so we can just parse an access letter. + cag_option_parse_access_letter(context, &c); + } + + // Move the items so that the options come first followed by non-option + // arguments. + cag_option_shift(context, old_index, new_index, context->index); + + return context->forced_end == false; +} + +char cag_option_get(const cag_option_context *context) +{ + // We just return the identifier here. + return context->identifier; +} + +const char *cag_option_get_value(const cag_option_context *context) +{ + // We just return the internal value pointer of the context. + return context->value; +} + +int cag_option_get_index(const cag_option_context *context) +{ + // Either we point to a value item, + return context->index; +} diff --git a/libs/cargs/test/definitions.h b/libs/cargs/test/definitions.h new file mode 100755 index 0000000..cdb820e --- /dev/null +++ b/libs/cargs/test/definitions.h @@ -0,0 +1,9 @@ +#pragma once + +#include "tests.h" +/** + * Creates prototypes for all test functions. + */ +#define XX(u, t) int u##_##t(void); +UNIT_TESTS(XX) +#undef XX diff --git a/libs/cargs/test/main.c b/libs/cargs/test/main.c new file mode 100755 index 0000000..229cf80 --- /dev/null +++ b/libs/cargs/test/main.c @@ -0,0 +1,105 @@ +#include "definitions.h" +#include +#include +#include +#include +#include + +struct cag_test +{ + const char *unit_name; + const char *test_name; + const char *full_name; + int (*fn)(void); +}; + +static struct cag_test tests[] = { +#define XX(u, t) \ + {.unit_name = #u, .test_name = #t, .full_name = #u "/" #t, .fn = u##_##t}, + UNIT_TESTS(XX) +#undef XX +}; + +static int call_test(struct cag_test *test) +{ + size_t i; + + printf(" Running '%s' ", test->full_name); + for (i = strlen(test->full_name); i < 40; ++i) { + fputs(".", stdout); + } + + if (test->fn() == EXIT_FAILURE) { + fputs(" FAILURE\n", stdout); + return EXIT_FAILURE; + } + + fputs(" SUCCESS\n", stdout); + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) +{ + size_t i, count, failed, succeeded; + const char *requested_unit_name, *requested_test_name; + struct cag_test *test; + double rate; + + count = 0; + failed = 0; + if (argc < 2) { + fputs("No unit specified. Running all tests.\n\n", stdout); + for (i = 0; i < CAG_ARRAY_SIZE(tests); ++i) { + test = &tests[i]; + ++count; + if (call_test(test) == EXIT_FAILURE) { + ++failed; + } + } + } else if (argc < 3) { + requested_unit_name = argv[1]; + printf("Running all unit tests of '%s'.\n\n", requested_unit_name); + for (i = 0; i < CAG_ARRAY_SIZE(tests); ++i) { + test = &tests[i]; + if (strcmp(test->unit_name, requested_unit_name) == 0) { + ++count; + if (call_test(test) == EXIT_FAILURE) { + ++failed; + } + } + } + } else { + requested_unit_name = argv[1]; + requested_test_name = argv[2]; + printf("Running a single test '%s/%s'.\n\n", requested_unit_name, + requested_test_name); + for (i = 0; i < CAG_ARRAY_SIZE(tests); ++i) { + test = &tests[i]; + if (strcmp(test->unit_name, requested_unit_name) == 0 && + strcmp(test->test_name, requested_test_name) == 0) { + ++count; + if (call_test(test) == EXIT_FAILURE) { + ++failed; + } + } + } + } + + if (count == 1) { + fputs("\nThe test has been executed.\n", stdout); + } else if (count > 0) { + succeeded = count - failed; + rate = (double)succeeded / (double)count * 100; + printf("\n%zu/%zu (%.2f%%) of those tests succeeded.\n", succeeded, count, + rate); + } else { + printf("\nNo tests found.\n"); + return EXIT_FAILURE; + } + + if (failed > 0) { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/libs/cargs/test/option_test.c b/libs/cargs/test/option_test.c new file mode 100755 index 0000000..c2dfff4 --- /dev/null +++ b/libs/cargs/test/option_test.c @@ -0,0 +1,665 @@ +#include "definitions.h" +#include +#include +#include +#include +#include +#include +#include + +static struct cag_option options[] = { + + {.identifier = 's', + .access_letters = "s", + .access_name = NULL, + .value_name = NULL, + .description = "Simple flag"}, + + {.identifier = 'a', + .access_letters = "a", + .access_name = NULL, + .value_name = NULL, + .description = "Another simple flag"}, + + {.identifier = 'm', + .access_letters = "mMoO", + .access_name = NULL, + .value_name = NULL, + .description = "Multiple access letters"}, + + {.identifier = 'l', + .access_letters = NULL, + .access_name = "long", + .value_name = NULL, + .description = "Long parameter name"}, + + {.identifier = 'k', + .access_letters = "k", + .access_name = "key", + .value_name = "VALUE", + .description = "Parameter value"}}; + +struct cag_result +{ + bool simple; + bool another; + bool multi_access; + bool long_parameter; + bool value_parameter; + bool unknown; + bool def; + const char *value; +}; + +static struct cag_result result; +static char **argv; +static int argc; + +static int make_args(const char *str) +{ + const char *c; + int argIndex, argStart, argLength; + + argc = 0; + c = str; + do { + if (*c == ' ' || *c == '\0') { + ++argc; + } + } while (*(c++)); + + argv = malloc(sizeof(char *) * (argc + 1)); + if (argv == NULL) { + return 1; + } + + c = str; + argIndex = 0; + argStart = 0; + argLength = 0; + do { + if (*c == ' ' || *c == '\0') { + argv[argIndex] = malloc(argLength + 1); + memcpy(argv[argIndex], &str[argStart], argLength); + argv[argIndex][argLength] = '\0'; + ++argIndex; + argStart += argLength + 1; + argLength = 0; + } else { + ++argLength; + } + + } while (*(c++)); + + argv[argc] = NULL; + + return 0; +} + +static void destroy_args() +{ + int i; + + for (i = 0; i < argc; ++i) { + free(argv[i]); + } + + free(argv); +} + +static int option_test_run(int currentArgc, char *currentArgv[]) +{ + int index; + char identifier; + cag_option_context context; + + cag_option_prepare(&context, options, CAG_ARRAY_SIZE(options), currentArgc, + currentArgv); + + memset(&result, 0, sizeof(result)); + + while (cag_option_fetch(&context)) { + identifier = cag_option_get(&context); + switch (identifier) { + case 's': + result.simple = true; + break; + case 'a': + result.another = true; + break; + case 'm': + result.multi_access = true; + break; + case 'l': + result.long_parameter = true; + break; + case 'k': + result.value_parameter = true; + result.value = cag_option_get_value(&context); + break; + case '?': + result.unknown = true; + break; + default: + result.def = true; + break; + } + } + + index = cag_option_get_index(&context); + if (cag_option_fetch(&context) != false) { + return -1; + } + + if (cag_option_get_index(&context) != index) { + return -1; + } + + return cag_option_get_index(&context); +} + +int option_complex(void) +{ + int status; + + status = make_args("test file1 -s -- -a -- a file"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (!result.simple || result.another || result.multi_access || + result.long_parameter || result.value_parameter || result.unknown || + result.def || result.value != NULL) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_mixed(void) +{ + int status, i; + + const char *values[] = {"file1", "file2", "mixed", "file3", "--", "-m", + "parameters", "file4"}; + + status = make_args( + "test -s file1 -k=value file2 -a mixed file3 -- -m parameters file4"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0 || argc - status != 8) { + goto err_wrong; + } + + for (i = 0; i < (int)CAG_ARRAY_SIZE(values); ++i) { + if (strcmp(argv[status + i], values[i]) != 0) { + goto err_wrong; + } + } + + if (!result.simple || !result.another || result.multi_access || + result.long_parameter || !result.value_parameter || result.unknown || + result.def || result.value == NULL || + strcmp(result.value, "value") != 0) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_ending(void) +{ + int status; + + status = make_args("test -s -- -a"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (!result.simple || result.another || result.multi_access || + result.long_parameter || result.value_parameter || result.unknown || + result.def || result.value != NULL) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_long_missing_value(void) +{ + int status; + + status = make_args("test --key"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (result.simple || result.another || result.multi_access || + result.long_parameter || !result.value_parameter || result.unknown || + result.def || result.value != NULL) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_short_missing_value(void) +{ + int status; + + status = make_args("test -k"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (result.simple || result.another || result.multi_access || + result.long_parameter || !result.value_parameter || result.unknown || + result.def || result.value != NULL) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_long_space_value(void) +{ + int status; + + status = make_args("test --key super_value"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (result.simple || result.another || result.multi_access || + result.long_parameter || !result.value_parameter || result.unknown || + result.def || result.value == NULL || + strcmp(result.value, "super_value") != 0) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_short_space_value(void) +{ + int status; + + status = make_args("test -k test_value"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (result.simple || result.another || result.multi_access || + result.long_parameter || !result.value_parameter || result.unknown || + result.def || result.value == NULL || + strcmp(result.value, "test_value") != 0) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_long_equal_value(void) +{ + int status; + + status = make_args("test --key=super_value"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (result.simple || result.another || result.multi_access || + result.long_parameter || !result.value_parameter || result.unknown || + result.def || result.value == NULL || + strcmp(result.value, "super_value") != 0) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_short_equal_value(void) +{ + int status; + + status = make_args("test -k=test_value"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (result.simple || result.another || result.multi_access || + result.long_parameter || !result.value_parameter || result.unknown || + result.def || result.value == NULL || + strcmp(result.value, "test_value") != 0) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_combined(void) +{ + int status; + + status = make_args("test -sma"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (!result.simple || !result.another || !result.multi_access || + result.long_parameter || result.value_parameter || result.unknown || + result.def || result.value != NULL) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_unknown_long(void) +{ + int status; + + status = make_args("test --unknown"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (result.simple || result.another || result.multi_access || + result.long_parameter || result.value_parameter || !result.unknown || + result.def || result.value != NULL) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_unknown_short(void) +{ + int status; + + status = make_args("test -u"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (result.simple || result.another || result.multi_access || + result.long_parameter || result.value_parameter || !result.unknown || + result.def || result.value != NULL) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_alias(void) +{ + int status; + + status = make_args("test -O"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (result.simple || result.another || !result.multi_access || + result.long_parameter || result.value_parameter || result.unknown || + result.def || result.value != NULL) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_simple_long(void) +{ + int status; + + status = make_args("test --long"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (result.simple || result.another || result.multi_access || + !result.long_parameter || result.value_parameter || result.unknown || + result.def || result.value != NULL) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_simple(void) +{ + int status; + + status = make_args("test -s"); + if (status != 0) { + goto err_setup; + } + + status = option_test_run(argc, argv); + if (status < 0) { + goto err_wrong; + } + + if (!result.simple || result.another || result.multi_access || + result.long_parameter || result.value_parameter || result.unknown || + result.def || result.value != NULL) { + goto err_wrong; + } + + destroy_args(); + + return EXIT_SUCCESS; + +err_wrong: + destroy_args(); +err_setup: + return EXIT_FAILURE; +} + +int option_print(void) +{ + char buf[255]; + const char *expected; + FILE *test_file; + + expected = " -s Simple flag\n" + " -a Another simple flag\n" + " -m, -M, -o, -O Multiple access letters\n" + " --long Long parameter name\n" + " -k, --key=VALUE Parameter value\n"; + + test_file = tmpfile(); + if (test_file == NULL) { + goto err_open; + } + + cag_option_print(options, CAG_ARRAY_SIZE(options), test_file); + if (fseek(test_file, 0, SEEK_SET) != 0) { + goto err_seek; + } + + if (fread(buf, sizeof(buf), 1, test_file) != 1 && feof(test_file) == 0) { + goto err_read; + } + + if (memcmp(buf, expected, strlen(expected)) != 0) { + goto err_test; + } + + fclose(test_file); + return EXIT_SUCCESS; + +err_test: +err_read: +err_seek: + fclose(test_file); +err_open: + return EXIT_FAILURE; +} diff --git a/src/gl/gl.c b/src/gl/gl.c index 4639d48..106befe 100644 --- a/src/gl/gl.c +++ b/src/gl/gl.c @@ -17,6 +17,8 @@ int gl_init(lua_State *L); int glad_init(lua_State *L); int gl_terminate(lua_State *L); int gl_get_error(lua_State *L); +int gl_enable(lua_State *L); +int gl_disable(lua_State *L); void setup_gl(lua_State *L, int honey_index) { @@ -26,6 +28,8 @@ void setup_gl(lua_State *L, int honey_index) hs_str_cfunc("InitGlad", glad_init), hs_str_cfunc("Terminate", gl_terminate), hs_str_cfunc("GetError", gl_get_error), + hs_str_cfunc("Enable", gl_enable), + hs_str_cfunc("Disable", gl_disable), /******** enums ********/ /* data types */ @@ -41,6 +45,9 @@ void setup_gl(lua_State *L, int honey_index) hs_str_int("INVALID_OPERATION", GL_INVALID_OPERATION), hs_str_int("INVALID_FRAMEBUFFER_OPERATION", GL_INVALID_FRAMEBUFFER_OPERATION), hs_str_int("OUT_OF_MEMORY", GL_OUT_OF_MEMORY), + + /* opengl capabilities */ + hs_str_int("DEPTH_TEST", GL_DEPTH_TEST), ); setup_shader(L, gl_index); @@ -81,3 +88,21 @@ int gl_get_error(lua_State *L) lua_pushinteger(L, glGetError()); return 1; } + + +int gl_enable(lua_State *L) +{ + lua_Integer cap; + hs_parse_args(L, hs_int(cap)); + glEnable(cap); + return 0; +} + + +int gl_disable(lua_State *L) +{ + lua_Integer cap; + hs_parse_args(L, hs_int(cap)); + glDisable(cap); + return 0; +} diff --git a/src/glm/glm.c b/src/glm/glm.c index 525a029..ca6238a 100644 --- a/src/glm/glm.c +++ b/src/glm/glm.c @@ -1,5 +1,6 @@ #include #include +#include "glm.h" void setup_glm(lua_State *L, int honey_index) diff --git a/src/main.c b/src/main.c index 5f5d399..304d91d 100644 --- a/src/main.c +++ b/src/main.c @@ -5,13 +5,22 @@ #include "gl/gl.h" #include "image/image.h" #include "glm/glm.h" +#include "options/options.h" int main(int argc, char **argv) { + /* parse command-line options */ + struct honey_options options; + int result = parse_options(&options, argc, argv); + if (result == EXIT_FAILURE) return 1; + else if (result == EXIT_SUCCESS) return 0; + + /* set up lua state */ lua_State *L = luaL_newstate(); luaL_openlibs(L); + /* load honey bindings */ lua_createtable(L, 0, 2); int honey_index = lua_gettop(L); setup_gl(L, honey_index); @@ -20,17 +29,22 @@ int main(int argc, char **argv) setup_glm(L, honey_index); lua_setglobal(L, "honey"); - int err = luaL_loadfile(L, "honey.lua"); + /* load main script */ + int err = luaL_loadfile(L, options.script_file); if (err != 0) { - printf("cannot open file!\n"); + printf("cannot open file '%s'\n", options.script_file); lua_close(L); return 0; } + + /* run */ err = hs_call(L, 0, 0); if (err != 0) { const char *err_str = lua_tostring(L, -1); printf("failed to run: \n%s\n", err_str); } + + /* clean up */ lua_close(L); return 0; } diff --git a/src/options/CMakeLists.txt b/src/options/CMakeLists.txt new file mode 100644 index 0000000..39ed7db --- /dev/null +++ b/src/options/CMakeLists.txt @@ -0,0 +1,5 @@ +project(honey_engine) + +target_sources(honey PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/options.c +) diff --git a/src/options/options.c b/src/options/options.c new file mode 100644 index 0000000..4488837 --- /dev/null +++ b/src/options/options.c @@ -0,0 +1,56 @@ +#include +#include +#include "options.h" + +static struct cag_option opts[] = { + { + .identifier = 's', + .access_letters = "s", + .access_name = "script", + .value_name = "SCRIPT_FILE", + .description = "The filename of the main script. (default: main.lua)" + }, + { + .identifier = 'h', + .access_letters = "h", + .access_name = "help", + .value_name = NULL, + .description = "Shows this help message" + }, +}; + + +void print_help(char *program_name) +{ + printf("usage: %s [OPTIONS]\n", program_name); + cag_option_print(opts, CAG_ARRAY_SIZE(opts), stdout); +} + + +enum outcomes_t parse_options(struct honey_options *options, int argc, char **argv) +{ + /* default values */ + options->script_file = "main.lua"; + + /* parse options */ + char id; + const char *value; + cag_option_context context; + + cag_option_prepare(&context, opts, CAG_ARRAY_SIZE(opts), argc, argv); + while(cag_option_fetch(&context)) { + id = cag_option_get(&context); + switch(id) { + case 's': + options->script_file = cag_option_get_value(&context); + break; + case 'h': + print_help(argv[0]); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + return CONTINUE_SUCCESS; +} diff --git a/src/options/options.h b/src/options/options.h new file mode 100644 index 0000000..cb07b1a --- /dev/null +++ b/src/options/options.h @@ -0,0 +1,16 @@ +#ifndef HONEY_OPTIONS_H +#define HONEY_OPTIONS_H + +struct honey_options { + const char *script_file; // main entry point +}; + +enum outcomes_t { + CONTINUE_SUCCESS, + EXIT_SUCCESS, + EXIT_FAILURE, +}; + +enum outcomes_t parse_options(struct honey_options *options, int argc, char **argv); + +#endif -- cgit v1.2.1