From fdb4e9884bbb22705f600f968298a42b0cdad104 Mon Sep 17 00:00:00 2001 From: Simon Gene Gottlieb Date: Wed, 3 May 2023 11:27:09 +0200 Subject: [PATCH] Feat/tdl integration (#94) [FEATURE] tdl integration --- .github/workflows/api.yml | 1 + .github/workflows/ci_coverage.yml | 3 +- .github/workflows/ci_linux.yml | 3 +- .github/workflows/ci_macos.yml | 3 +- .github/workflows/ci_misc.yml | 5 +- .github/workflows/documentation.yaml | 2 + .gitmodules | 3 + build_system/sharg-config.cmake | 43 ++- include/sharg/detail/format_base.hpp | 2 +- include/sharg/detail/format_tdl.hpp | 357 ++++++++++++++++++ include/sharg/parser.hpp | 30 +- submodules/tool_description_lib | 1 + test/snippet/readme_sneak_peek.out | 3 +- test/unit/detail/CMakeLists.txt | 2 + test/unit/detail/format_ctd_test.cpp | 155 ++++++++ test/unit/detail/format_cwl_test.cpp | 279 ++++++++++++++ test/unit/detail/format_help_test.cpp | 3 +- test/unit/detail/format_html_test.cpp | 4 +- test/unit/detail/format_man_test.cpp | 4 +- test/unit/detail/seqan3_test.cpp | 3 +- .../parser/format_parse_validators_test.cpp | 3 +- 21 files changed, 871 insertions(+), 38 deletions(-) create mode 100644 include/sharg/detail/format_tdl.hpp create mode 160000 submodules/tool_description_lib create mode 100644 test/unit/detail/format_ctd_test.cpp create mode 100644 test/unit/detail/format_cwl_test.cpp diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml index b99a0cbd..c0fb448a 100644 --- a/.github/workflows/api.yml +++ b/.github/workflows/api.yml @@ -38,6 +38,7 @@ jobs: with: path: sharg fetch-depth: 1 + submodules: true # To reuse scripts - name: Checkout SeqAn3 diff --git a/.github/workflows/ci_coverage.yml b/.github/workflows/ci_coverage.yml index 49bf4b63..d49a6d8d 100644 --- a/.github/workflows/ci_coverage.yml +++ b/.github/workflows/ci_coverage.yml @@ -55,6 +55,7 @@ jobs: with: path: sharg fetch-depth: ${{ steps.fetch_depth.outputs.depth }} + submodules: true # To reuse scripts - name: Checkout SeqAn3 @@ -115,7 +116,7 @@ jobs: -DCMAKE_CXX_FLAGS="${{ matrix.cxx_flags }}" \ -DSHARG_VERBOSE_TESTS=OFF \ -DSHARG_COVERAGE_PARALLEL_LEVEL=2 - make -j2 gtest_build + make -j2 gtest_build yaml-cpp - name: Build tests env: diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 44e62c78..222e1016 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -61,6 +61,7 @@ jobs: with: path: sharg fetch-depth: 1 + submodules: true # To reuse scripts - name: Checkout SeqAn3 @@ -113,7 +114,7 @@ jobs: cmake ../sharg/test/${{ matrix.build }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ -DCMAKE_CXX_FLAGS="${{ matrix.cxx_flags }}" \ -DSHARG_VERBOSE_TESTS=OFF - make -j2 gtest_build + make -j2 gtest_build yaml-cpp - name: Build tests env: diff --git a/.github/workflows/ci_macos.yml b/.github/workflows/ci_macos.yml index 4795dbc1..cd3d186b 100644 --- a/.github/workflows/ci_macos.yml +++ b/.github/workflows/ci_macos.yml @@ -61,6 +61,7 @@ jobs: with: path: sharg fetch-depth: 1 + submodules: true # To reuse scripts - name: Checkout SeqAn3 @@ -113,7 +114,7 @@ jobs: cmake ../sharg/test/${{ matrix.build }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ -DCMAKE_CXX_FLAGS="${{ matrix.cxx_flags }}" \ -DSHARG_VERBOSE_TESTS=OFF - make -j3 gtest_build + make -j3 gtest_build yaml-cpp - name: Build tests env: diff --git a/.github/workflows/ci_misc.yml b/.github/workflows/ci_misc.yml index c4bdc70e..0afbc306 100644 --- a/.github/workflows/ci_misc.yml +++ b/.github/workflows/ci_misc.yml @@ -93,6 +93,7 @@ jobs: with: path: sharg fetch-depth: 1 + submodules: true # To reuse scripts - name: Checkout SeqAn3 @@ -159,8 +160,8 @@ jobs: -DCMAKE_CXX_FLAGS="${{ matrix.cxx_flags }}" \ -DSHARG_VERBOSE_TESTS=OFF case "${{ matrix.build }}" in - snippet) make -j${{ matrix.build_threads }} gtest_build;; - header) make -j${{ matrix.build_threads }} gtest_build gbenchmark_build;; + snippet) make -j${{ matrix.build_threads }} gtest_build yaml-cpp;; + header) make -j${{ matrix.build_threads }} gtest_build gbenchmark_build yaml-cpp;; documentation) make -j${{ matrix.build_threads }} download-cppreference-doxygen-web-tag;; esac diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index 0d90d23a..91143211 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -29,6 +29,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + with: + submodules: true # To reuse scripts - name: Checkout SeqAn3 diff --git a/.gitmodules b/.gitmodules index e69de29b..c7750963 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/tool_description_lib"] + path = submodules/tool_description_lib + url = https://github.com/deNBI-cibi/tool_description_lib.git diff --git a/build_system/sharg-config.cmake b/build_system/sharg-config.cmake index 1a334c09..9b91d699 100644 --- a/build_system/sharg-config.cmake +++ b/build_system/sharg-config.cmake @@ -118,7 +118,7 @@ endmacro () # Note that sharg-config.cmake can be standalone and thus SHARG_CLONE_DIR might be empty. # * `SHARG_CLONE_DIR` was already found in sharg-config-version.cmake # * `SHARG_INCLUDE_DIR` was already found in sharg-config-version.cmake -find_path (SHARG_SUBMODULES_DIR NAMES lib/seqan3 HINTS "${SHARG_CLONE_DIR}" "${SHARG_INCLUDE_DIR}/sharg") +find_path (SHARG_SUBMODULES_DIR NAMES submodules/tool_description_lib HINTS "${SHARG_CLONE_DIR}" "${SHARG_INCLUDE_DIR}/sharg") if (SHARG_INCLUDE_DIR) sharg_config_print ("SHARG include dir found: ${SHARG_INCLUDE_DIR}") @@ -134,15 +134,16 @@ if (SHARG_CLONE_DIR) sharg_config_print ("Detected as running from a repository checkout…") endif () -if (SHARG_SUBMODULES_DIR) - file (GLOB submodules ${SHARG_SUBMODULES_DIR}/lib/*/include) - foreach (submodule ${submodules}) - if (IS_DIRECTORY ${submodule}) - sharg_config_print (" …adding submodule include: ${submodule}") - set (SHARG_DEPENDENCY_INCLUDE_DIRS ${submodule} ${SHARG_DEPENDENCY_INCLUDE_DIRS}) - endif () - endforeach () -endif () +# Currently unused. +# if (SHARG_SUBMODULES_DIR) +# file (GLOB submodules ${SHARG_SUBMODULES_DIR}/lib/*/include) +# foreach (submodule ${submodules}) +# if (IS_DIRECTORY ${submodule}) +# sharg_config_print (" …adding submodule include: ${submodule}") +# set (SHARG_DEPENDENCY_INCLUDE_DIRS ${submodule} ${SHARG_DEPENDENCY_INCLUDE_DIRS}) +# endif () +# endforeach () +# endif () # ---------------------------------------------------------------------------- # Options for CheckCXXSourceCompiles @@ -211,6 +212,22 @@ else () sharg_config_print ("Thread support: not found.") endif () +# ---------------------------------------------------------------------------- +# tool description lib (tdl) dependency +# ---------------------------------------------------------------------------- + +set (STORED_CMAKE_MESSAGE_LOG_LEVEL "${CMAKE_MESSAGE_LOG_LEVEL}") +set (CMAKE_MESSAGE_LOG_LEVEL "ERROR") +find_package (TDL QUIET HINTS ${SHARG_SUBMODULES_DIR}/submodules/tool_description_lib ${SHARG_HINT_TDL}) +set (CMAKE_MESSAGE_LOG_LEVEL "${STORED_CMAKE_MESSAGE_LOG_LEVEL}") +unset (STORED_CMAKE_MESSAGE_LOG_LEVEL) + +if (TDL_FOUND) + sharg_config_print ("Dependency: TDL found.") +else () + sharg_config_error ("Dependency: TDL not found.") +endif () + # ---------------------------------------------------------------------------- # ZLIB dependency # ---------------------------------------------------------------------------- @@ -297,9 +314,9 @@ try_compile (SHARG_PLATFORM_TEST OUTPUT_VARIABLE SHARG_PLATFORM_TEST_OUTPUT) if (SHARG_PLATFORM_TEST) - sharg_config_print ("SHARG platform.hpp build: passed.") + sharg_config_print ("SHARG platform.hpp build: passed.") else () - sharg_config_error ("SHARG platform.hpp build: failed!\n\ + sharg_config_error ("SHARG platform.hpp build: failed!\n\ ${SHARG_PLATFORM_TEST_OUTPUT}") endif () @@ -329,7 +346,7 @@ if (SHARG_FOUND AND NOT TARGET sharg::sharg) add_library (sharg_sharg INTERFACE) target_compile_definitions (sharg_sharg INTERFACE ${SHARG_DEFINITIONS}) target_compile_options (sharg_sharg INTERFACE ${SHARG_CXX_FLAGS_LIST}) - target_link_libraries (sharg_sharg INTERFACE "${SHARG_LIBRARIES}") + target_link_libraries (sharg_sharg INTERFACE "${SHARG_LIBRARIES}" tdl::tdl) # include sharg/include/ as -I, because sharg should never produce warnings. target_include_directories (sharg_sharg INTERFACE "${SHARG_INCLUDE_DIR}") # include everything except sharg/include/ as -isystem, i.e. diff --git a/include/sharg/detail/format_base.hpp b/include/sharg/detail/format_base.hpp index 5f32a29e..0bd42cc7 100644 --- a/include/sharg/detail/format_base.hpp +++ b/include/sharg/detail/format_base.hpp @@ -340,7 +340,7 @@ class format_help_base : public format_base derived_t().print_list_item("\\fB--version\\fP", "Prints the version information."); derived_t().print_list_item("\\fB--copyright\\fP", "Prints the copyright/license information."); derived_t().print_list_item("\\fB--export-help\\fP (std::string)", - "Export the help page information. Value must be one of [html, man]."); + "Export the help page information. Value must be one of [html, man, ctd, cwl]."); if (version_check_dev_decision == update_notifications::on) derived_t().print_list_item("\\fB--version-check\\fP (bool)", "Whether to check for the newest app version. Default: true."); diff --git a/include/sharg/detail/format_tdl.hpp b/include/sharg/detail/format_tdl.hpp new file mode 100644 index 00000000..21c91e92 --- /dev/null +++ b/include/sharg/detail/format_tdl.hpp @@ -0,0 +1,357 @@ +// ----------------------------------------------------------------------------------------------------------- +// Copyright (c) 2006-2023, Knut Reinert & Freie Universität Berlin +// Copyright (c) 2016-2023, Knut Reinert & MPI für molekulare Genetik +// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License +// shipped with this file and also available at: https://github.com/seqan/sharg-parser/blob/master/LICENSE.md +// ----------------------------------------------------------------------------------------------------------- + +/*!\file + * \author Simon Gene Gottlieb + * \brief Provides the format_tdl struct and its helper functions. + */ + +#pragma once + +#include +#include + +#include +#include + +#include +#include + +namespace sharg::detail +{ + +/*!\brief converts a value into the corresponding tdl value type + * \param v The value to convert. + * \return the matching tdl type. + */ +inline auto to_tdl(bool v) +{ + return tdl::BoolValue(v); +} + +//!\copydetails sharg::detail::to_tdl +auto to_tdl(std::integral auto v) +{ + return tdl::IntValue(v); +} + +//!\copydetails sharg::detail::to_tdl +auto to_tdl(std::floating_point auto v) +{ + return tdl::DoubleValue(v); +} + +//!\copydetails sharg::detail::to_tdl +inline auto to_tdl(std::string const & v) +{ + return tdl::StringValue(v); +} + +//!\copydetails sharg::detail::to_tdl +auto to_tdl(auto SHARG_DOXYGEN_ONLY(v)) +{ + return tdl::BoolValue(false); +} + +/*!\brief A generalized format to create different tool description files. + * \ingroup parser + * + * \details + * + * This class allows to create different outputs format. See sharg::detail::format_tdl::FileFormat for + * available formats. + */ +class format_tdl : format_base +{ +public: + //!\brief Supported tool description file formats. + enum class FileFormat + { + CTD, //!> parser_set_up_calls; + //!\brief Vector of functions that stores add_positional_option calls. + std::vector> positional_option_calls; // singled out to be printed on top + //!\brief Keeps track of the number of positional options + unsigned positional_option_count{0}; + //!\brief The names of subcommand programs. + std::vector command_names{}; + + //!\brief TDL DS filled with tool meta information + tdl::ToolInfo info; + + //!\brief Targeted tool description format + FileFormat fileFormat; + + /*!\brief Stores all meta information about the application + * + * \details + * + * This needs to be a member of format_parse, because it needs to present + * (not filled) when the parser_set_up_calls vector is filled, since all + * printing functions need some meta information. + * The member variable itself is filled when copied over from the argument_parser + * when calling format_parse::parse. That way all the information needed are + * there, when the actual printing starts. + * + * This function is not private because it is needed for short but nicely + * formatted (error) output to the command line. + */ + parser_meta_data meta; + +public: + /*!\name Constructors, destructor and assignment + * \{ + */ + /*!\brief Construct from a file format. + * \param fileFormat The file format. + */ + format_tdl(FileFormat fileFormat) : fileFormat{fileFormat} + {} + + format_tdl(format_tdl const &) = default; //!< Defaulted. + format_tdl & operator=(format_tdl const &) = default; //!< Defaulted. + format_tdl(format_tdl &&) = default; //!< Defaulted. + format_tdl & operator=(format_tdl &&) = default; //!< Defaulted. + ~format_tdl() = default; //!< Defaulted. + + /*!\brief Adds a sharg::print_list_item call to be evaluated later on. + * \copydetails sharg::parser::add_option + */ + template + void add_option(option_type & value, config_type const & config) + { + auto description = config.description; + description += (config.required ? std::string{" "} : detail::to_string(" Default: ", value, ". ")); + description += config.validator.get_help_page_message(); + + auto tags = std::set{}; + if (config.required) + { + tags.insert("required"); + } + if (config.advanced) + { + tags.insert("advanced"); + } + if constexpr (std::same_as) + { + auto valueAsStr = to_string(value); + store_help_page_element( + [this, config, description, valueAsStr, _tags = tags](std::string_view) + { + auto tags = _tags; + + // Check if validator is a file,directory,input and/or output paremeter + using Validator = std::decay_t; + if constexpr (std::is_base_of_v) + { + tags.insert("file"); + } + else if constexpr (std::is_base_of_v) + { + tags.insert("directory"); + } + else if constexpr (std::is_base_of_v) + { + tags.insert("file"); + tags.insert("output"); + } + else if constexpr (std::is_base_of_v) + { + tags.insert("directory"); + tags.insert("output"); + } + + info.params.push_back(tdl::Node{ + .name = config.long_id, + .description = description, + .tags = std::move(tags), + .value = tdl::StringValue{valueAsStr}, + }); + info.cliMapping.emplace_back("--" + config.long_id, config.long_id); + }, + config); + } + else + { + store_help_page_element( + [this, config, value, description, tags](std::string_view) + { + info.params.push_back(tdl::Node{ + .name = config.long_id, + .description = description, + .tags = std::move(tags), + .value = to_tdl(value), + }); + info.cliMapping.emplace_back("--" + config.long_id, config.long_id); + }, + config); + } + } + + /*!\brief Adds a sharg::print_list_item call to be evaluated later on. + * \copydetails sharg::parser::add_flag + */ + template + void add_flag(bool & value, config_type const & config) + { + store_help_page_element( + [this, config, value](std::string_view) + { + info.params.push_back(tdl::Node{ + .name = config.long_id, + .description = config.description, + .tags = {}, + .value = to_tdl(value), + }); + }, + config); + } + + /*!\brief Adds a sharg::print_list_item call to be evaluated later on. + * \copydetails sharg::parser::add_positional_option + */ + template + void add_positional_option(option_type & value, config_type const & config) + { + std::string msg = config.validator.get_help_page_message(); + + positional_option_calls.push_back( + [this, &value, config, msg](std::string_view) + { + auto id = "positional_" + std::to_string(positional_option_count); + ++positional_option_count; + auto description = + config.description + + // a list at the end may be empty and thus have a default value + ((detail::is_container_option) ? detail::to_string(" Default: ", value, ". ") + : std::string{" "}) + + msg; + + info.params.push_back(tdl::Node{ + .name = id, + .description = description, + .tags = {}, + .value = tdl::StringValue{}, + }); + if (!config.long_id.empty()) + { + info.cliMapping.emplace_back("--" + config.long_id, config.long_id); + } + }); + } + + /*!\brief Initiates the printing of the help page to std::cout. + * \param[in] parser_meta The meta information that are needed for a detailed help page. + * \param[in] executable_name A list of arguments that form together the call to the executable. + * For example: [raptor, build] + */ + void parse(parser_meta_data & parser_meta, std::vector const & executable_name) + { + meta = parser_meta; + + // each call will evaluate the function print_list_item() + for (auto f : positional_option_calls) + f(meta.app_name); + + // each call will evaluate the function print_list_item() + for (auto f : parser_set_up_calls) + f(meta.app_name); + + info.metaInfo = tdl::MetaInfo{ + .version = meta.version, + .name = meta.app_name, + .docurl = meta.url, + .category = "", + .description = std::accumulate(begin(meta.description), + end(meta.description), + std::string{}, + [](auto a, auto v) + { + return a + v + '\n'; + }), + // .citations = {meta.citation}, + }; + if (!executable_name.empty()) + { + info.metaInfo.executableName = executable_name[0]; + } + for (size_t i{1}; i < executable_name.size(); ++i) + { + auto name = "subcommand_" + std::to_string(i); + info.params.push_back(tdl::Node{ + .name = name, + .value = tdl::StringValue(executable_name[i]), + }); + info.cliMapping.emplace_back("", name); + } + + if (fileFormat == FileFormat::CTD) + { + std::cout << tdl::convertToCTD(info); + } + else if (fileFormat == FileFormat::CWL) + { + std::cout << tdl::convertToCWL(info) << "\n"; + } + else + { + throw std::runtime_error("unsupported file format (this is a bug)"); + } + std::exit(EXIT_SUCCESS); // program should not continue from here + } + + /*!\brief Adds a print_section call to parser_set_up_calls. + * \copydetails sharg::parser::add_section + */ + void add_section(std::string const & SHARG_DOXYGEN_ONLY(title), bool const SHARG_DOXYGEN_ONLY(advanced_only)) + {} + + /*!\brief Adds a print_subsection call to parser_set_up_calls. + * \copydetails sharg::parser::add_subsection + */ + void add_subsection(std::string const & SHARG_DOXYGEN_ONLY(title), bool const SHARG_DOXYGEN_ONLY(advanced_only)) + {} + + /*!\brief Adds a print_line call to parser_set_up_calls. + * \copydetails sharg::parser::add_line + */ + void add_line(std::string const & SHARG_DOXYGEN_ONLY(text), + bool SHARG_DOXYGEN_ONLY(is_paragraph), + bool const SHARG_DOXYGEN_ONLY(advanced_only)) + {} + + /*!\brief Adds a sharg::print_list_item call to parser_set_up_calls. + * \copydetails sharg::parser::add_list_item + */ + void add_list_item(std::string const & SHARG_DOXYGEN_ONLY(key), + std::string const & SHARG_DOXYGEN_ONLY(desc), + bool const SHARG_DOXYGEN_ONLY(advanced_only)) + {} + +private: + /*!\brief Adds a function object to parser_set_up_calls **if** the annotation in `spec` does not prevent it. + * \param[in] printer The invokable that, if added to `parser_set_up_calls`, prints information to the help page. + * \param[in] config The option specification deciding whether to add the information to the help page. + * + * \details + * + * If `spec` equals `sharg::option_spec::hidden`, the information is never added to the help page. + */ + void store_help_page_element(std::function printer, config const & config) + { + if (config.hidden) + return; + parser_set_up_calls.push_back(std::move(printer)); + } +}; + +} // namespace sharg::detail diff --git a/include/sharg/parser.hpp b/include/sharg/parser.hpp index 935fccaa..e4299cf5 100644 --- a/include/sharg/parser.hpp +++ b/include/sharg/parser.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include namespace sharg @@ -432,11 +433,17 @@ class parser version_check_future = app_version_prom.get_future(); app_version(std::move(app_version_prom)); } - std::visit( - [this](auto & f) + [this](T & f) { - f.parse(info); + if constexpr (std::same_as) + { + f.parse(info, executable_name); + } + else + { + f.parse(info); + } }, format); parse_was_called = true; @@ -710,6 +717,7 @@ class parser detail::format_version, detail::format_html, detail::format_man, + detail::format_tdl, detail::format_copyright/*, detail::format_ctd*/> format{detail::format_help{{}, {}, false}}; // Will be overwritten in any case. @@ -748,11 +756,11 @@ class parser * - \--version sets the format to sharg::detail::format_version. * - \--export-help html sets the format to sharg::detail::format_html. * - \--export-help man sets the format to sharg::detail::format_man. - * - \--export-help ctd sets the format to sharg::detail::format_ctd. + * - \--export-help cwl sets the format to sharg::detail::format_tdl{FileFormat::CWL}. + * - \--export-help ctd sets the format to sharg::detail::format_tdl{FileFormat::CTD}. * - else the format is that to sharg::detail::format_parse * - * If `--export-help` is specified with a value other than html/man or ctd - * an sharg::parser_error is thrown. + * If `--export-help` is specified with a value other than html, man, cwl or ctd, an sharg::parser_error is thrown. */ void init(int argc, char const * const * const argv) { @@ -827,13 +835,13 @@ class parser format = detail::format_html{subcommands, version_check_dev_decision}; else if (export_format == "man") format = detail::format_man{subcommands, version_check_dev_decision}; - // TODO (smehringer) use when CTD support is available - // else if (export_format == "ctd") - // format = detail::format_ctd{}; + else if (export_format == "ctd") + format = detail::format_tdl{detail::format_tdl::FileFormat::CTD}; + else if (export_format == "cwl") + format = detail::format_tdl{detail::format_tdl::FileFormat::CWL}; else throw validation_error{"Validation failed for option --export-help: " - "Value must be one of [html, man]"}; - + "Value must be one of [html, man, ctd, cwl]."}; special_format_was_set = true; } else if (arg == "--copyright") diff --git a/submodules/tool_description_lib b/submodules/tool_description_lib new file mode 160000 index 00000000..584ccd39 --- /dev/null +++ b/submodules/tool_description_lib @@ -0,0 +1 @@ +Subproject commit 584ccd39f7968dc86022a6fcbbe0e2be963f98ce diff --git a/test/snippet/readme_sneak_peek.out b/test/snippet/readme_sneak_peek.out index f6a72e4e..a67ba920 100644 --- a/test/snippet/readme_sneak_peek.out +++ b/test/snippet/readme_sneak_peek.out @@ -17,7 +17,8 @@ OPTIONS --copyright Prints the copyright/license information. --export-help (std::string) - Export the help page information. Value must be one of [html, man]. + Export the help page information. Value must be one of [html, man, + ctd, cwl]. --version-check (bool) Whether to check for the newest app version. Default: true. diff --git a/test/unit/detail/CMakeLists.txt b/test/unit/detail/CMakeLists.txt index 962f7670..edc8d6bf 100644 --- a/test/unit/detail/CMakeLists.txt +++ b/test/unit/detail/CMakeLists.txt @@ -16,6 +16,8 @@ sharg_test(format_html_test.cpp CYCLIC_DEPENDING_INCLUDES include-sharg-detail-format_help.hpp include-sharg-detail-format_man.hpp) sharg_test(format_man_test.cpp) +sharg_test(format_ctd_test.cpp) +sharg_test(format_cwl_test.cpp) sharg_test(safe_filesystem_entry_test.cpp) sharg_test(type_name_as_string_test.cpp) sharg_test(version_check_debug_test.cpp) diff --git a/test/unit/detail/format_ctd_test.cpp b/test/unit/detail/format_ctd_test.cpp new file mode 100644 index 00000000..fdb4418e --- /dev/null +++ b/test/unit/detail/format_ctd_test.cpp @@ -0,0 +1,155 @@ +// ----------------------------------------------------------------------------------------------------------- +// Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin +// Copyright (c) 2016-2021, Knut Reinert & MPI für molekulare Genetik +// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License +// shipped with this file and also available at: https://github.com/seqan/sharg-parser/blob/master/LICENSE.md +// ----------------------------------------------------------------------------------------------------------- + +#include + +#include + +// Reused global variables +struct format_ctd_test : public ::testing::Test +{ + int option_value{5}; + bool flag_value{false}; + int8_t non_list_pos_opt_value{1}; + std::vector list_pos_opt_value{}; + std::string my_stdout{}; + static constexpr std::array argv{"./format_ctd_test", "--version-check", "false", "--export-help", "ctd"}; + std::string const version_str{sharg::sharg_version_cstring}; + std::string expected = + R"del()del" + "\n" + R"del()del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del( )del" + "\n" + R"del()del" + "\n"; + + // Full info parser initialisation + void dummy_init(sharg::parser & parser) + { + parser.info.date = "December 01, 1994"; + parser.info.version = "01.01.01"; + parser.info.man_page_title = "default_ctd_page_title"; + parser.info.short_description = "A short description here."; + parser.info.synopsis.push_back("./format_ctd_test synopsis"); + parser.info.synopsis.push_back("./format_ctd_test synopsis2"); + parser.info.description.push_back("description"); + parser.info.description.push_back("description2"); + parser.add_option(option_value, sharg::config{'i', "int", "this is a int option."}); + parser.add_option(option_value, + sharg::config{.short_id = 'j', + .long_id = "jint", + .description = "this is a required int option.", + .required = true}); + parser.add_section("Flags"); + parser.add_subsection("SubFlags"); + parser.add_line("here come all the flags"); + parser.add_flag(flag_value, sharg::config{'f', "flag", "this is a flag."}); + parser.add_flag(flag_value, sharg::config{'k', "kflag", "this is a flag."}); + parser.add_positional_option(non_list_pos_opt_value, + sharg::config{.description = "this is a positional option."}); + parser.add_positional_option(list_pos_opt_value, sharg::config{.description = "this is a positional option."}); + parser.info.examples.push_back("example"); + parser.info.examples.push_back("example2"); + } +}; + +TEST_F(format_ctd_test, empty_information) +{ + // Create the dummy parser. + sharg::parser parser{"default", argv.size(), argv.data()}; + parser.info.date = "December 01, 1994"; + parser.info.version = "1.0.1-rc.1"; + parser.info.man_page_title = "default_man_page_title"; + parser.info.short_description = "A short description here."; + + std::string const version_str{sharg::sharg_version_cstring}; + std::string expected_short = + R"()" + "\n" + R"()" + "\n" + R"( )" + "\n" + R"( )" + "\n" + R"( )" + "\n" + R"()" + "\n"; + + // Test the dummy parser with minimal information. + testing::internal::CaptureStdout(); + EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); + + my_stdout = testing::internal::GetCapturedStdout(); + EXPECT_EQ(my_stdout, expected_short); +} + +TEST_F(format_ctd_test, full_information) +{ + // Create the dummy parser. + sharg::parser parser{"default", argv.size(), argv.data()}; + + // Fill out the dummy parser with options and flags and sections and subsections. + dummy_init(parser); + // Test the dummy parser without any copyright or citations. + testing::internal::CaptureStdout(); + EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); + + my_stdout = testing::internal::GetCapturedStdout(); + EXPECT_EQ(my_stdout, expected); +} diff --git a/test/unit/detail/format_cwl_test.cpp b/test/unit/detail/format_cwl_test.cpp new file mode 100644 index 00000000..5ecbe226 --- /dev/null +++ b/test/unit/detail/format_cwl_test.cpp @@ -0,0 +1,279 @@ +// ----------------------------------------------------------------------------------------------------------- +// Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin +// Copyright (c) 2016-2021, Knut Reinert & MPI für molekulare Genetik +// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License +// shipped with this file and also available at: https://github.com/seqan/sharg-parser/blob/master/LICENSE.md +// ----------------------------------------------------------------------------------------------------------- + +#include + +#include + +TEST(format_cwl_test, empty_information) +{ + auto argv = std::array{"./format_cwl_test", "--version-check", "false", "--export-help", "cwl"}; + + // Create the dummy parser. + auto parser = sharg::parser{"default", argv.size(), argv.data()}; + parser.info.date = "December 01, 1994"; + parser.info.version = "1.0.1-rc.1"; + parser.info.man_page_title = "default_man_page_title"; + parser.info.short_description = "A short description here."; + + std::string expected_short = "inputs:\n" + " []\n" + "outputs:\n" + " []\n" + "label: default\n" + "doc: \"\"\n" + "cwlVersion: v1.2\n" + "class: CommandLineTool\n" + "baseCommand:\n" + " - format_cwl_test\n"; + + // Test the dummy parser with minimal information. + testing::internal::CaptureStdout(); + EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); + + auto my_stdout = testing::internal::GetCapturedStdout(); + EXPECT_EQ(my_stdout, expected_short); +} + +TEST(format_cwl_test, full_information) +{ + auto argv = std::array{"./format_cwl_test", "--version-check", "false", "--export-help", "cwl"}; + + // Create variables for the arguments + int option_value{5}; + bool flag_value{false}; + int8_t non_list_pos_opt_value{1}; + auto list_pos_opt_value = std::vector{}; + + // Create the dummy parser. + auto parser = sharg::parser{"default", argv.size(), argv.data()}; + parser.info.date = "December 01, 1994"; + parser.info.version = "01.01.01"; + parser.info.man_page_title = "default_ctd_page_title"; + parser.info.short_description = "A short description here."; + parser.info.synopsis.push_back("./format_cwl_test synopsis"); + parser.info.synopsis.push_back("./format_cwl_test synopsis2"); + parser.info.description.push_back("description"); + parser.info.description.push_back("description2"); + parser.add_option(option_value, sharg::config{'i', "int", "this is a int option."}); + parser.add_option(option_value, + sharg::config{.short_id = 'j', + .long_id = "jint", + .description = "this is a required int option.", + .required = true}); + parser.add_section("Flags"); + parser.add_subsection("SubFlags"); + parser.add_line("here come all the flags"); + parser.add_flag(flag_value, sharg::config{'f', "flag", "this is a flag."}); + parser.add_flag(flag_value, sharg::config{'k', "kflag", "this is a flag."}); + parser.add_positional_option(non_list_pos_opt_value, sharg::config{.description = "this is a positional option."}); + parser.add_positional_option(list_pos_opt_value, sharg::config{.description = "this is a positional option."}); + parser.info.examples.push_back("example"); + parser.info.examples.push_back("example2"); + + std::string expected_short = "inputs:\n" + " - doc: \"this is a int option. Default: 5. \"\n" + " id: int\n" + " type:\n" + " - \"null\"\n" + " - long\n" + " inputBinding:\n" + " prefix: --int\n" + " - doc: \"this is a required int option. \"\n" + " id: jint\n" + " type: long\n" + " inputBinding:\n" + " prefix: --jint\n" + "outputs:\n" + " []\n" + "label: default\n" + "doc: \"description\\ndescription2\\n\"\n" + "cwlVersion: v1.2\n" + "class: CommandLineTool\n" + "baseCommand:\n" + " - format_cwl_test\n"; + + // Test the dummy parser with minimal information. + testing::internal::CaptureStdout(); + EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); + + auto my_stdout = testing::internal::GetCapturedStdout(); + EXPECT_EQ(my_stdout, expected_short); +} + +TEST(format_cwl_test, subparser) +{ + auto argv = std::array{"./format_cwl_test", "index", "--export-help", "cwl"}; + + // Create variables for the arguments + int option_value{5}; + float option_value_float{0}; + std::string option_value_string{}; + std::filesystem::path option_value_path{}; + bool flag_value{false}; + int8_t non_list_pos_opt_value{1}; + auto list_pos_opt_value = std::vector{}; + + // Create the dummy parser. + auto parser = sharg::parser{"default", argv.size(), argv.data(), sharg::update_notifications::off, {"index"}}; + parser.info.date = "December 01, 1994"; + parser.info.version = "01.01.01"; + parser.info.man_page_title = "default_ctd_page_title"; + parser.info.short_description = "A short description here."; + parser.info.synopsis.push_back("./format_cwl_test synopsis"); + parser.info.synopsis.push_back("./format_cwl_test synopsis2"); + parser.info.description.push_back("description"); + parser.info.description.push_back("description2"); + + parser.parse(); + auto & sub_parser = parser.get_sub_parser(); + if (sub_parser.info.app_name != std::string_view{"default-index"}) + { + return; + } + sub_parser.add_option(option_value, sharg::config{'i', "int", "this is a int option."}); + sub_parser.add_option(option_value, + sharg::config{.short_id = 'j', + .long_id = "jint", + .description = "this is a required int option.", + .required = true}); + sub_parser.add_option(option_value_float, + sharg::config{.short_id = 'p', + .long_id = "percent", + .description = "this is a required float option.", + .required = true}); + sub_parser.add_option(option_value_string, + sharg::config{.short_id = 's', + .long_id = "string", + .description = "this is a string option (advanced).", + .advanced = true, + .required = false}); + sub_parser.add_option(option_value_string, + sharg::config{.short_id = '\0', + .long_id = "hide", + .description = "a hidden option, that doesn't show up.", + .hidden = true}); + sub_parser.add_option(option_value_path, + sharg::config{.short_id = '\0', + .long_id = "path01", + .description = "a normal file.", + .validator = sharg::input_file_validator{}}); + sub_parser.add_option(option_value_path, + sharg::config{.short_id = '\0', + .long_id = "path02", + .description = "a normal file with a valid file extension.", + .validator = sharg::input_file_validator{{".fa", ".fasta"}}}); + sub_parser.add_option(option_value_path, + sharg::config{.short_id = '\0', + .long_id = "path03", + .description = "a input directory.", + .validator = sharg::input_directory_validator{}}); + sub_parser.add_option(option_value_path, + sharg::config{.short_id = '\0', + .long_id = "path04", + .description = "a output file.", + .validator = sharg::output_file_validator{}}); + sub_parser.add_option(option_value_path, + sharg::config{.short_id = '\0', + .long_id = "path05", + .description = "a output directory.", + .validator = sharg::output_directory_validator{}}); + + sub_parser.add_section("Flags"); + sub_parser.add_subsection("SubFlags"); + sub_parser.add_line("here come all the flags"); + sub_parser.add_flag(flag_value, sharg::config{'f', "flag", "this is a flag."}); + sub_parser.add_flag(flag_value, sharg::config{'k', "kflag", "this is a flag."}); + sub_parser.add_positional_option(non_list_pos_opt_value, + sharg::config{.description = "this is a positional option."}); + sub_parser.add_positional_option(list_pos_opt_value, sharg::config{.description = "this is a positional option."}); + sub_parser.info.examples.push_back("example"); + sub_parser.info.examples.push_back("example2"); + + std::string expected_short = + "inputs:\n" + " - doc: \"this is a int option. Default: 5. \"\n" + " id: int\n" + " type:\n" + " - \"null\"\n" + " - long\n" + " inputBinding:\n" + " prefix: --int\n" + " - doc: \"this is a required int option. \"\n" + " id: jint\n" + " type: long\n" + " inputBinding:\n" + " prefix: --jint\n" + " - doc: \"this is a required float option. \"\n" + " id: percent\n" + " type: double\n" + " inputBinding:\n" + " prefix: --percent\n" + " - doc: \"this is a string option (advanced). Default: . \"\n" + " id: string\n" + " type:\n" + " - \"null\"\n" + " - string\n" + " inputBinding:\n" + " prefix: --string\n" + " - doc: \"a normal file. Default: \\\"\\\". The input file must exist and read permissions must be " + "granted.\"\n" + " id: path01\n" + " type:\n" + " - \"null\"\n" + " - File\n" + " inputBinding:\n" + " prefix: --path01\n" + " - doc: \"a normal file with a valid file extension. Default: \\\"\\\". The input file must exist and read " + "permissions must be granted. Valid file extensions are: [.fa, .fasta].\"\n" + " id: path02\n" + " type:\n" + " - \"null\"\n" + " - File\n" + " inputBinding:\n" + " prefix: --path02\n" + " - doc: \"a input directory. Default: \\\"\\\". An existing, readable path for the input directory.\"\n" + " id: path03\n" + " type:\n" + " - \"null\"\n" + " - Directory\n" + " inputBinding:\n" + " prefix: --path03\n" + " - doc: \"a output file. Default: \\\"\\\". The output file must not exist already and write permissions " + "must be granted.\"\n" + " id: path04\n" + " type: string\n" + " inputBinding:\n" + " prefix: --path04\n" + " - doc: \"a output directory. Default: \\\"\\\". A valid path for the output directory.\"\n" + " id: path05\n" + " type: string\n" + " inputBinding:\n" + " prefix: --path05\n" + "outputs:\n" + " - id: path04\n" + " type: File\n" + " outputBinding:\n" + " glob: $(inputs.path04)\n" + " - id: path05\n" + " type: Directory\n" + " outputBinding:\n" + " glob: $(inputs.path05)\n" + "label: default-index\n" + "doc: \"\"\n" + "cwlVersion: v1.2\n" + "class: CommandLineTool\n" + "baseCommand:\n" + " - format_cwl_test\n" + " - index\n"; + testing::internal::CaptureStdout(); + // sub_parser.parse(); + EXPECT_EXIT(sub_parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); + + auto my_stdout = testing::internal::GetCapturedStdout(); + EXPECT_EQ(my_stdout, expected_short); +} diff --git a/test/unit/detail/format_help_test.cpp b/test/unit/detail/format_help_test.cpp index e1d39bba..60ec7605 100644 --- a/test/unit/detail/format_help_test.cpp +++ b/test/unit/detail/format_help_test.cpp @@ -32,7 +32,8 @@ std::string const basic_options_str = " Common options\n" " --copyright\n" " Prints the copyright/license information.\n" " --export-help (std::string)\n" - " Export the help page information. Value must be one of [html, man].\n" + " Export the help page information. Value must be one of [html, man,\n" + " ctd, cwl].\n" " --version-check (bool)\n" " Whether to check for the newest app version. Default: true.\n"; diff --git a/test/unit/detail/format_html_test.cpp b/test/unit/detail/format_html_test.cpp index 2b268240..a0f7d0ab 100644 --- a/test/unit/detail/format_html_test.cpp +++ b/test/unit/detail/format_html_test.cpp @@ -42,7 +42,7 @@ TEST(html_format, empty_information) "
--copyright
\n" "
Prints the copyright/license information.
\n" "
--export-help (std::string)
\n" - "
Export the help page information. Value must be one of [html, man].
\n" + "
Export the help page information. Value must be one of [html, man, ctd, cwl].
\n" "
--version-check (bool)
\n" "
Whether to check for the newest app version. Default: true.
\n" "\n" @@ -164,7 +164,7 @@ TEST(html_format, full_information_information) "
--copyright
\n" "
Prints the copyright/license information.
\n" "
--export-help (std::string)
\n" - "
Export the help page information. Value must be one of [html, man].
\n" + "
Export the help page information. Value must be one of [html, man, ctd, cwl].
\n" "
--version-check (bool)
\n" "
Whether to check for the newest app version. Default: true.
\n" "\n" diff --git a/test/unit/detail/format_man_test.cpp b/test/unit/detail/format_man_test.cpp index 83e76820..98278864 100644 --- a/test/unit/detail/format_man_test.cpp +++ b/test/unit/detail/format_man_test.cpp @@ -117,7 +117,7 @@ struct format_man_test : public ::testing::Test "\n" R"(\fB--export-help\fP (std::string))" "\n" - R"(Export the help page information. Value must be one of [html, man].)" + R"(Export the help page information. Value must be one of [html, man, ctd, cwl].)" "\n" R"(.TP)" "\n" @@ -230,7 +230,7 @@ TEST_F(format_man_test, empty_information) "\n" R"(\fB--export-help\fP (std::string))" "\n" - R"(Export the help page information. Value must be one of [html, man].)" + R"(Export the help page information. Value must be one of [html, man, ctd, cwl].)" "\n" R"(.TP)" "\n" diff --git a/test/unit/detail/seqan3_test.cpp b/test/unit/detail/seqan3_test.cpp index 38cb2789..c1329cb3 100644 --- a/test/unit/detail/seqan3_test.cpp +++ b/test/unit/detail/seqan3_test.cpp @@ -19,7 +19,8 @@ std::string const basic_options_str = " Common options\n" " --copyright\n" " Prints the copyright/license information.\n" " --export-help (std::string)\n" - " Export the help page information. Value must be one of [html, man].\n" + " Export the help page information. Value must be one of [html, man,\n" + " ctd, cwl].\n" " --version-check (bool)\n" " Whether to check for the newest app version. Default: true.\n"; diff --git a/test/unit/parser/format_parse_validators_test.cpp b/test/unit/parser/format_parse_validators_test.cpp index acc06bbe..cade0f9e 100644 --- a/test/unit/parser/format_parse_validators_test.cpp +++ b/test/unit/parser/format_parse_validators_test.cpp @@ -23,7 +23,8 @@ std::string const basic_options_str = " Common options\n" " --copyright\n" " Prints the copyright/license information.\n" " --export-help (std::string)\n" - " Export the help page information. Value must be one of [html, man].\n"; + " Export the help page information. Value must be one of [html, man,\n" + " ctd, cwl].\n"; std::string const basic_version_str = "VERSION\n" " Last update:\n"