diff --git a/.travis.yml b/.travis.yml index 54880c1..70389f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,12 +16,18 @@ install: - sudo update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-10 90 # install cmake - CMAKE_VERSION="3.18" - - CMAKE_FULL="${CMAKE_VERSION}.2" + - CMAKE_FULL="${CMAKE_VERSION}.4" - pushd ~ - curl "https://cmake.org/files/v${CMAKE_VERSION}/cmake-${CMAKE_FULL}-Linux-x86_64.tar.gz" -o "cmake-${CMAKE_FULL}-Linux-x86_64.tar.gz" - tar xf "cmake-${CMAKE_FULL}-Linux-x86_64.tar.gz" - export PATH="${PWD}/cmake-${CMAKE_FULL}-Linux-x86_64/bin:${PATH}" - popd + - pyenv global 3.8 + - python -m pip install -U conan + - export PATH=$PATH:`python -c "import site; import os; print(':'.join([os.path.abspath(os.path.join(dir, '..', '..', '..', 'bin')) for dir in site.getsitepackages()]))"` + - conan user + - conan profile new default --detect + - conan profile update settings.compiler.libcxx=libstdc++11 default before_script: # verify installed versions @@ -39,6 +45,7 @@ before_script: script: - mkdir build - cd build + - conan install .. --build missing -s build_type=Debug - cmake -DCMAKE_BUILD_TYPE=Debug -DLIBARGS_COVERALLS=ON -DLIBARGS_COVERALLS_UPLOAD=ON .. - make -j`nproc` - make coveralls diff --git a/CMakeLists.txt b/CMakeLists.txt index a5850cb..35199c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ -cmake_minimum_required (VERSION 3.12) -project (args - VERSION 0.12.0 +cmake_minimum_required(VERSION 3.12) +project(args + VERSION 0.12.1 DESCRIPTION "" LANGUAGES CXX) @@ -8,6 +8,10 @@ set(PROJECT_VERSION_STABILITY "") # or "-alpha", or "-beta", or "-rc.5" if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) message(STATUS "Libargs: Standalone") + + include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) + conan_basic_setup(TARGETS) + set(LIBARG_TESTING_DEFAULT ON) set(LIBARG_INSTALL_DEFAULT ON) set_property(GLOBAL PROPERTY USE_FOLDERS ON) @@ -25,6 +29,7 @@ endif() set(LIBARGS_TESTING ${LIBARG_TESTING_DEFAULT} CACHE BOOL "Compile and/or run self-tests") set(LIBARGS_INSTALL ${LIBARG_INSTALL_DEFAULT} CACHE BOOL "Install the library") +set(LIBARGS_SHARED OFF CACHE BOOL "Build an .so instead of the .a archive") if (LIBARGS_TESTING) find_package(Python3 COMPONENTS Interpreter REQUIRED) @@ -95,18 +100,21 @@ endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/version.in" "${CMAKE_CURRENT_BINARY_DIR}/include/args/version.hpp" @ONLY) -add_library(args STATIC +set(BUILD_SHARED_LIBS ${LIBARGS_SHARED}) +add_library(args src/actions.cpp src/parser.cpp src/printer.cpp src/translator.cpp include/args/actions.hpp + include/args/api.hpp include/args/parser.hpp include/args/printer.hpp include/args/translator.hpp "${CMAKE_CURRENT_BINARY_DIR}/include/args/version.hpp" ) target_compile_options(args PRIVATE ${ADDITIONAL_WALL_FLAGS}) +target_compile_definitions(args PRIVATE LIBARGS_EXPORTING) target_compile_features(args PRIVATE cxx_std_17) target_include_directories(args PUBLIC @@ -114,6 +122,10 @@ target_include_directories(args ${CMAKE_CURRENT_BINARY_DIR}/include PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) +if (LIBARGS_SHARED) + target_compile_definitions(args PUBLIC LIBARGS_SHARED) +endif() + include(CheckCXXSourceCompiles) function(check_charconv) check_cxx_source_compiles("#include diff --git a/README.md b/README.md index d4f0687..44d13da 100644 --- a/README.md +++ b/README.md @@ -14,30 +14,96 @@ The argument values can be stored in: - enums, with little help from library user (uses `std::is_enum` and needs `args::enum_traits` provided), as well as - `std::optional`, `std::vector` and `std::unordered_set` of the things on this list. +## Config + +Type of all CMake variables blow is `BOOL`. + +|Project variable|Comment| +|----------------|-------| +|`LIBARGS_TESTING`|Compile and/or run self-tests. _Adds `test` target (`RUN_TESTS` on MSVC), which runs automatic tests._ | +|`LIBARGS_INSTALL`|Install the library. _Adds `install` target (`INSTALL` on MSVC), which installs all distributable files inside `CMAKE_INSTALL_PREFIX` directory. On MSVC it operates on `Release` configuration._ | +|`LIBARGS_SHARED`|Build an .so instead of the .a archive. _Setting this variable is still experimental. Since this is pre-1.0 code, installing a shared library system-wide is not advised in the first place, in addition there is little experience in using this library as a shared entity._ | +|`LIBARGS_COVERALLS`|Turn on coveralls support. _Ads `coveralls` target, which runs tests and gathers coverage results in the Coveralls.io compatible JSON file. This variable is only available, if `LIBARGS_TESTING` is true. On Ubuntu, it works only with `gcov`, `llvm`-based solution is not available. On MSVC it operates on `Debug` configuration and requires [OpenCppCoverage](https://github.com/OpenCppCoverage/OpenCppCoverage) installed and on `%PATH%`._| + ## Building +Conan is used for proper `MD`/`MDd`/`MT`/`MTd` setup. This project does not require any dependencies otherwise. The `` used in recipes below is either `Debug` or `Release`. + +### Ubuntu + ```sh mkdir build && cd build -cmake .. -G Ninja -DLIBARGS_TESTING=OFF -DLIBARGS_INSTALL=OFF +conan install .. -s build_type= +cmake .. \ + -G Ninja \ + -DLIBARGS_TESTING=OFF \ + -DLIBARGS_INSTALL=OFF \ + -DCMAKE_BUILD_TYPE= ninja ``` +### Windows + +The `` below is either `MD` or `MT` for `Release`, or `MDd` or `MTd` for `Debug`. If `LIBARGS_SHARED` is set to `ON`, then `MD`/`MDd` is strongly advised. + +```sh +mkdir build && cd build +conan install .. -s build_type= -s compiler.runtime= +cmake .. -G Ninja -DLIBARGS_TESTING=OFF -DLIBARGS_INSTALL=OFF +cmake --build . --target ALL_BUILD -- ^ + /nologo /v:m /p:Configuration= +``` + ## Testing +### Ubuntu + ```sh mkdir build && cd build -cmake .. -G Ninja -DLIBARGS_TESTING=ON +conan install .. -s build_type= +cmake .. \ + -G Ninja \ + -DLIBARGS_TESTING=ON \ + -DLIBARGS_INSTALL=OFF \ + -DCMAKE_BUILD_TYPE= ninja && ninja test ``` +### Windows + +```sh +mkdir build && cd build +conan install .. -s build_type= -s compiler.runtime= +cmake .. -G Ninja -DLIBARGS_TESTING=ON -DLIBARGS_INSTALL=OFF +cmake --build . --target RUN_TESTS -- ^ + /nologo /v:m /p:Configuration= +``` + ## Installing +### Ubuntu + ```sh mkdir build && cd build -cmake .. -G Ninja -DLIBARGS_TESTING=OFF -DLIBARGS_INSTALL=ON +conan install .. -s build_type=Release +cmake .. \ + -G Ninja \ + -DLIBARGS_TESTING=OFF \ + -DLIBARGS_INSTALL=ON \ + -DCMAKE_BUILD_TYPE=Release ninja && sudo ninja install ``` +### Windows + +```sh +mkdir build && cd build +conan install .. -s build_type=Release -s compiler.runtime=MD +cmake .. -G Ninja -DLIBARGS_TESTING=OFF -DLIBARGS_INSTALL=ON +cmake --build . --target RUN_TESTS -- ^ + /nologo /v:m /p:Configuration=Release +``` + ## Example To mimic Python example: @@ -115,7 +181,7 @@ prog: error: argument N is required The arguments with values can have those values either separated by a space or can have it glued to the name of the argument; in case of long names, glued values need to be separated from names by a `"="`. -|Width|Example|Seperated|Glued|Standalone| +|Width|Example|Separated|Glued|Standalone| |-----|-------|--------------|-----|-------------| |Long|`"arg"`|`--arg value`|`--arg=value`|`--arg`| |Short|`"c"`, `"z"`, `"f"`|`-f value`|`-fvalue`|`-c -z`| @@ -190,7 +256,7 @@ Extension point for the enum arguments, helping convert strings from command lin - `ENUM_TRAITS_NAME(enum-value)` or - `ENUM_TRAITS_NAME_EX(enum-value, "string-to-use")` - which provide the string-to-enum mapping; one-argument version uses `enum-name` as scope for `enum-value`, while two-argument version is much lower level and the value is left unscoped. + which provide the string-to-enum mapping; one-argument version uses `enum-name` as scope for `enum-value`, while two-argument version is much lower level and the value is left not scoped. 3. `ENUM_TRAITS_END(enum-name)` \ finishes all the work started by `ENUM_TRAITS_BEGIN(enum-name)`. diff --git a/conanfile.txt b/conanfile.txt new file mode 100644 index 0000000..9a8edec --- /dev/null +++ b/conanfile.txt @@ -0,0 +1,4 @@ +[requires] + +[generators] +cmake \ No newline at end of file diff --git a/include/args/actions.hpp b/include/args/actions.hpp index d2ab997..6454fdc 100644 --- a/include/args/actions.hpp +++ b/include/args/actions.hpp @@ -23,19 +23,24 @@ #endif #endif +#include + namespace args { class parser; struct base_translator; namespace actions { - [[noreturn]] void argument_is_not_integer(parser& p, - std::string const& name); - [[noreturn]] void argument_out_of_range(parser& p, - std::string const& name); - [[noreturn]] void enum_argument_out_of_range(parser& p, - std::string const& name, - std::string const& value, - std::string const& values); + [[noreturn]] LIBARGS_API void argument_is_not_integer( + parser& p, + std::string const& name); + [[noreturn]] LIBARGS_API void argument_out_of_range( + parser& p, + std::string const& name); + [[noreturn]] LIBARGS_API void enum_argument_out_of_range( + parser& p, + std::string const& name, + std::string const& value, + std::string const& values); } // namespace actions template @@ -202,7 +207,7 @@ namespace args { }; namespace actions { - struct action { + struct LIBARGS_API action { virtual ~action(); virtual bool required() const = 0; virtual void required(bool value) = 0; @@ -291,7 +296,7 @@ namespace args { void visited(bool val) { visited_ = val; } - std::string argname(parser&) const; + LIBARGS_API std::string argname(parser&) const; public: void required(bool value) override { required_ = value; } @@ -305,7 +310,8 @@ namespace args { } bool visited() const override { return visited_; } void meta(std::string_view s) override { meta_ = s; } - std::string meta(base_translator const& _) const override; + LIBARGS_API std::string meta( + base_translator const& _) const override; void help(std::string_view s) override { help_ = s; } std::string const& help() const override { return help_; } diff --git a/include/args/api.hpp b/include/args/api.hpp new file mode 100644 index 0000000..34ddec0 --- /dev/null +++ b/include/args/api.hpp @@ -0,0 +1,22 @@ +// Copyright (c) 2018 midnightBITS +// This code is licensed under MIT license (see LICENSE for details) + +#pragma once + +#if defined(_WIN32) +#define LIBARGS_EXPORT __declspec(dllexport) +#define LIBARGS_IMPORT __declspec(dllimport) +#else +#define LIBARGS_EXPORT +#define LIBARGS_IMPORT +#endif + +#if defined(LIBARGS_SHARED) +#if defined(LIBARGS_EXPORTING) +#define LIBARGS_API LIBARGS_EXPORT +#else +#define LIBARGS_API LIBARGS_IMPORT +#endif +#else +#define LIBARGS_API +#endif diff --git a/include/args/parser.hpp b/include/args/parser.hpp index ae47cdb..201408b 100644 --- a/include/args/parser.hpp +++ b/include/args/parser.hpp @@ -41,7 +41,8 @@ namespace args { return {count_ - n, args_ + n}; } - static std::string_view program_name(std::string_view arg0) noexcept; + LIBARGS_API static std::string_view program_name( + std::string_view arg0) noexcept; }; struct args_view { @@ -186,11 +187,11 @@ namespace args { std::move(cb), std::forward(names)...); } - void program(std::string const& value); - std::string const& program() const noexcept; + LIBARGS_API void program(std::string const& value); + LIBARGS_API std::string const& program() const noexcept; - void usage(std::string_view value); - std::string const& usage() const noexcept; + LIBARGS_API void usage(std::string_view value); + LIBARGS_API std::string const& usage() const noexcept; void provide_help(bool value = true) { provide_help_ = value; } bool provides_help() const noexcept { return provide_help_; } @@ -211,89 +212,20 @@ namespace args { return parse_width_; } - arglist parse(unknown_action on_unknown = exclusive_parser, - std::optional maybe_width = {}); - - void printer_append_usage(std::string& out) const; - fmt_list printer_arguments() const; - - void short_help(FILE* out = stdout, - bool for_error = false, - std::optional maybe_width = {}) const; - [[noreturn]] void help(std::optional maybe_width = {}) const; - [[noreturn]] void error(std::string const& msg, - std::optional maybe_width = {}) const; + LIBARGS_API arglist parse(unknown_action on_unknown = exclusive_parser, + std::optional maybe_width = {}); + + LIBARGS_API void printer_append_usage(std::string& out) const; + LIBARGS_API fmt_list printer_arguments() const; + + LIBARGS_API void short_help( + FILE* out = stdout, + bool for_error = false, + std::optional maybe_width = {}) const; + [[noreturn]] LIBARGS_API void help( + std::optional maybe_width = {}) const; + [[noreturn]] LIBARGS_API void error( + std::string const& msg, + std::optional maybe_width = {}) const; }; -#if defined(HAS_STD_CONCEPTS) - static_assert(StringLike); - static_assert(StringLike); - static_assert(StringLike); - static_assert(StringLike); - static_assert(StringLike); - static_assert(StringLike); - static_assert(StringLike); - static_assert(StringLike); - static_assert(StringLike); - static_assert(StringLike); - static_assert(StringLike); - static_assert(StringLike); - static_assert(!StringLike); - static_assert(!StringLike>); - static_assert(!StringLike); - static_assert(!StringLike); - static_assert(AnyActionHandler); - static_assert(AnyActionHandler); - static_assert(AnyActionHandler); - static_assert(!AnyActionHandler); - - static_assert(AnyActionHandler); - static_assert(AnyActionHandler); - static_assert(AnyActionHandler); - static_assert(!AnyActionHandler); - - static_assert(!AnyActionHandler); - static_assert(!AnyActionHandler); - static_assert(!AnyActionHandler); - - namespace detail::static_tests { -#define CONCAT2(A, B) A##_##B -#define CONCAT(A, B) CONCAT2(A, B) -#define STRUCT_TEST(ARGS, NOT) \ - struct CONCAT(Test, __LINE__) { \ - void operator() ARGS; \ - }; \ - static_assert(NOT AnyActionHandler) - -#define FAILING_TEST(ARGS) STRUCT_TEST(ARGS, !) -#define NOTHING -#define SUCCEEDING_TEST(ARGS) STRUCT_TEST(ARGS, NOTHING) - - class None {}; - static_assert(!AnyActionHandler); - - SUCCEEDING_TEST(()); - SUCCEEDING_TEST((parser&)); - SUCCEEDING_TEST((std::string const&)); - SUCCEEDING_TEST((std::string)); - SUCCEEDING_TEST((parser&, std::string const&)); - SUCCEEDING_TEST((parser&, std::string)); - SUCCEEDING_TEST((std::string_view const&)); - SUCCEEDING_TEST((std::string_view)); - SUCCEEDING_TEST((parser&, std::string_view const&)); - SUCCEEDING_TEST((parser&, std::string_view)); - - FAILING_TEST((parser)); - FAILING_TEST((std::string&)); - FAILING_TEST((parser&, std::string&)); - FAILING_TEST((parser, std::string const&)); - - FAILING_TEST((int)); - FAILING_TEST((parser&, int)); - FAILING_TEST((std::string const&, int)); - FAILING_TEST((parser&, std::string const&, int)); - FAILING_TEST((int, parser&)); - FAILING_TEST((int, std::string const&)); - FAILING_TEST((int, parser&, std::string const&)); - } // namespace detail::static_tests -#endif } // namespace args diff --git a/include/args/printer.hpp b/include/args/printer.hpp index cd001a9..060f7ce 100644 --- a/include/args/printer.hpp +++ b/include/args/printer.hpp @@ -6,11 +6,12 @@ #include #include #include +#include namespace args { namespace detail { - bool is_terminal(FILE* out) noexcept; - size_t terminal_width(FILE* out) noexcept; + LIBARGS_API bool is_terminal(FILE* out) noexcept; + LIBARGS_API size_t terminal_width(FILE* out) noexcept; template inline It split(It cur, It end, size_t width) noexcept { diff --git a/include/args/translator.hpp b/include/args/translator.hpp index 2a601ba..58ffedd 100644 --- a/include/args/translator.hpp +++ b/include/args/translator.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace args { enum class lng : int { @@ -25,7 +26,7 @@ namespace args { file_not_found }; - struct base_translator { + struct LIBARGS_API base_translator { virtual ~base_translator(); base_translator(); base_translator(base_translator const&) = delete; @@ -37,7 +38,7 @@ namespace args { std::string_view arg2 = {}) const = 0; }; - class null_translator : public base_translator { + class LIBARGS_API null_translator : public base_translator { public: std::string operator()(lng id, std::string_view arg1, diff --git a/src/parser.cpp b/src/parser.cpp index 530f997..b07c6be 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -342,8 +342,6 @@ bool args::parser::parse_short(ArgList& list, unknown_action on_unknown) { continue; } - std::string param; - ++index; if (index < length) { auto param = argument.substr(index); @@ -384,3 +382,77 @@ bool args::parser::parse_answer_file(std::string const& path, if (list.options.fail()) error(_(lng::file_not_found, path), parse_width_); return parse_list(list, on_unknown); } + +#if defined(HAS_STD_CONCEPTS) +static_assert(args::StringLike); +static_assert(args::StringLike); +static_assert(args::StringLike); +static_assert(args::StringLike); +static_assert(args::StringLike); +static_assert(args::StringLike); +static_assert(args::StringLike); +static_assert(args::StringLike); +static_assert(args::StringLike); +static_assert(args::StringLike); +static_assert(args::StringLike); +static_assert(args::StringLike); +static_assert(!args::StringLike); +static_assert(!args::StringLike>); +static_assert(!args::StringLike); +static_assert(!args::StringLike); +static_assert(args::AnyActionHandler); +static_assert(args::AnyActionHandler); +static_assert(args::AnyActionHandler); +static_assert(!args::AnyActionHandler); + +static_assert(args::AnyActionHandler); +static_assert(args::AnyActionHandler); +static_assert( + args::AnyActionHandler); +static_assert(!args::AnyActionHandler); + +static_assert(!args::AnyActionHandler); +static_assert(!args::AnyActionHandler); +static_assert(!args::AnyActionHandler); + +namespace detail::static_tests { +#define CONCAT2(A, B) A##_##B +#define CONCAT(A, B) CONCAT2(A, B) +#define STRUCT_TEST(ARGS, NOT) \ + struct CONCAT(Test, __LINE__) { \ + void operator() ARGS; \ + }; \ + static_assert(NOT args::AnyActionHandler) + +#define FAILING_TEST(ARGS) STRUCT_TEST(ARGS, !) +#define NOTHING +#define SUCCEEDING_TEST(ARGS) STRUCT_TEST(ARGS, NOTHING) + + class None {}; + static_assert(!args::AnyActionHandler); + + SUCCEEDING_TEST(()); + SUCCEEDING_TEST((args::parser&)); + SUCCEEDING_TEST((std::string const&)); + SUCCEEDING_TEST((std::string)); + SUCCEEDING_TEST((args::parser&, std::string const&)); + SUCCEEDING_TEST((args::parser&, std::string)); + SUCCEEDING_TEST((std::string_view const&)); + SUCCEEDING_TEST((std::string_view)); + SUCCEEDING_TEST((args::parser&, std::string_view const&)); + SUCCEEDING_TEST((args::parser&, std::string_view)); + + FAILING_TEST((args::parser)); + FAILING_TEST((std::string&)); + FAILING_TEST((args::parser&, std::string&)); + FAILING_TEST((args::parser, std::string const&)); + + FAILING_TEST((int)); + FAILING_TEST((args::parser&, int)); + FAILING_TEST((std::string const&, int)); + FAILING_TEST((args::parser&, std::string const&, int)); + FAILING_TEST((int, args::parser&)); + FAILING_TEST((int, std::string const&)); + FAILING_TEST((int, args::parser&, std::string const&)); +} // namespace detail::static_tests +#endif