diff --git a/utilities/ArgumentParsingLib/ArgumentParsingLib.tar.gz b/utilities/ArgumentParsingLib/ArgumentParsingLib.tar.gz deleted file mode 100644 index 01dffb5..0000000 Binary files a/utilities/ArgumentParsingLib/ArgumentParsingLib.tar.gz and /dev/null differ diff --git a/utilities/ArgumentParsingLib/ArgumentParsingLibConfig.cmake.in b/utilities/ArgumentParsingLib/ArgumentParsingLibConfig.cmake.in new file mode 100644 index 0000000..112c300 --- /dev/null +++ b/utilities/ArgumentParsingLib/ArgumentParsingLibConfig.cmake.in @@ -0,0 +1,6 @@ +SET(ArgumentParsingLib_FOUND 1) +SET(ArgumentParsingLib_SOURCE_DIR "@ArgumentParsingLib_SOURCE_DIR@") +SET(ArgumentParsingLib_BINARY_DIR "@ArgumentParsingLib_BINARY_DIR@") +SET(ArgumentParsingLib_Boost_INCLUDE_DIR "@Boost_INCLUDE_DIR@") +SET(ArgumentParsingLib_Boost_LIBRARY_DIR "@Boost_LIBRARY_DIRS@") +SET(ArgumentParsingLib_Boost_LIBRARIES "@Boost_LIBRARIES@") \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/CMakeLists.txt b/utilities/ArgumentParsingLib/CMakeLists.txt new file mode 100644 index 0000000..88555f0 --- /dev/null +++ b/utilities/ArgumentParsingLib/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(ArgumentParsingLib) + +# Disallow compilers that don't support the necessary C++11 features (since the property CXX_STANDARD doesn't work with MSVC) +if(MSVC AND MSVC_VERSION LESS 1800) + message(FATAL_ERROR "The C++11 features in this project require Visual Studio 2013 or higher") +endif() + +set(BUILD_TESTS false CACHE BOOL "Build tests") +set(MAIN_DIR main) +set(TEST_DIR test) + +# Sources AND tests both need boost, so include it here +IF (WIN32) + SET(Boost_USE_STATIC_LIBS ON) +ELSE() + SET(Boost_USE_STATIC_LIBS OFF) +ENDIF() +find_package(Boost REQUIRED COMPONENTS program_options) +include_directories(${Boost_INCLUDE_DIRS}) +link_directories(${Boost_LIBRARY_DIRS}) + +add_subdirectory(${MAIN_DIR}) +add_subdirectory(${TEST_DIR}) + +# Create a file by which this project can be found +CONFIGURE_FILE(${ArgumentParsingLib_SOURCE_DIR}/ArgumentParsingLibConfig.cmake.in + ${ArgumentParsingLib_BINARY_DIR}/ArgumentParsingLibConfig.cmake @ONLY IMMEDIATE) \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/main/CMakeLists.txt b/utilities/ArgumentParsingLib/main/CMakeLists.txt new file mode 100644 index 0000000..9a25d08 --- /dev/null +++ b/utilities/ArgumentParsingLib/main/CMakeLists.txt @@ -0,0 +1,10 @@ +add_library(${PROJECT_NAME} STATIC parseXML.cpp parseXML.h XMLGenerator.cpp XMLGenerator.h CmdLineParserBase.cpp CmdLineParserBase.h) + +# MSVC automatically links to boost +if(NOT MSVC) + target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES}) +endif() + +# Enable C++11 support in Linux +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/main/CmdLineParserBase.cpp b/utilities/ArgumentParsingLib/main/CmdLineParserBase.cpp new file mode 100644 index 0000000..f398ea1 --- /dev/null +++ b/utilities/ArgumentParsingLib/main/CmdLineParserBase.cpp @@ -0,0 +1,259 @@ +//------------------------------------------------------------------------ +// Copyright(c) German Cancer Research Center(DKFZ), +// Software Development for Integrated Diagnostics and Therapy (SIDT). +// ALL RIGHTS RESERVED. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notices for more information. +// +// Authors: Sascha Diatschuk, Simon Strubel, Clemens Hentschke, Fabrizio Ciccarese +//------------------------------------------------------------------------ + +#include "CmdLineParserBase.h" +#include "parseXML.h" +#include "boost/any.hpp" + +namespace po = boost::program_options; + +namespace cmdlineparsing +{ + + CmdLineParserBase::CmdLineParserBase(const std::string& programName, const std::string& version, const std::string& programDescription, + const std::string& programContributor, const std::string& programCategory) : + m_programCategory(programCategory), m_programName(programName), m_programDescription(programDescription), m_programVersion(version), m_programContributor(programContributor), m_success(false) + { + addOption(OPTION_HELP, OPTION_GROUP_GENERIC, "Display this help", 'h'); + addOption(OPTION_VERSION, OPTION_GROUP_GENERIC, "Display the program version", 'v'); + addOption(OPTION_XML, OPTION_GROUP_GENERIC, "Outputs a xml description of all options", 'x'); + addOption(OPTION_CONFIG_FILE, OPTION_GROUP_GENERIC, + "A configuration file containing the parameters to this " + "program. Can be XML or simple text format.", 'c'); + } + + void CmdLineParserBase::addOption(const std::string& name, + const std::string& optionGroupName, + const std::string& description, char shorthand) + { + std::string fullName = name; + + if (shorthand != 0) + { + fullName += "," + std::string(1, shorthand); + } + + getOrCreateOptionGroup(optionGroupName).add_options()(fullName.c_str(), + description.c_str()); + std::tuple arguments = make_tuple(shorthand, description, optionGroupName, "" ); + + findDuplicate(getOptionsAsStringMap(), name, shorthand); + + m_baseOptionsMap[name] = arguments; + } + + void CmdLineParserBase::findDuplicate(const std::unordered_map >& optionsMap, + const std::string& name, char shorthand) const + { + for (const auto& option : optionsMap) + { + if (shorthand == 0) + { + if (option.first == name) + { + throw boost::program_options::duplicate_option_error("Duplicated shorthand or option name"); + } + } + else + { + if (option.first == name || std::get<0>(option.second) == shorthand) + { + throw boost::program_options::duplicate_option_error("Duplicated shorthand or option name"); + } + } + } + } + + void CmdLineParserBase::addPositionalOption(const std::string& name, int numArgs) + { + m_positionalOptions.add(name.c_str(), numArgs); + } + + void CmdLineParserBase::parse(int argc, const char* argv[]) + { + // Relocate the "Generic Arguments" option group so it's always displayed last in + // the help + m_optionGroupMap.relocate(m_optionGroupMap.get().end(), + m_optionGroupMap.get().begin()); + + // Add all option groups in the same order they have been defined + for (const auto& optionGroup : m_optionGroupMap.get()) + { + m_cmdLineOptions.add(optionGroup.second); + + // The arguments from the generic option group aren't parsable by the config + // file. + if (optionGroup.first != OPTION_GROUP_GENERIC) + { + m_configFileOptions.add(optionGroup.second); + } + } + + store(po::command_line_parser(argc, argv) + .options(m_cmdLineOptions) + .positional(m_positionalOptions) + .run(), + m_varMap); + + // Check the help or version flag before the auto-validation so other options are ignored. + if (m_varMap.count(OPTION_HELP)) + { + printHelp(); + return; + } + else if (m_varMap.count(OPTION_VERSION)) + { + printVersion(); + return; + } + else if (m_varMap.count(OPTION_XML)) + { + printXML(); + return; + } + + if (m_varMap.count(OPTION_CONFIG_FILE)) + { + std::string configFile = m_varMap[OPTION_CONFIG_FILE].as(); + + if (boost::algorithm::iends_with(boost::algorithm::to_lower_copy(configFile), + ".xml")) + { + store(parseXML(configFile, m_configFileOptions), m_varMap); + } + else + { + store(po::parse_config_file(configFile.c_str(), m_configFileOptions), + m_varMap); + } + } + + // Auto-validation + notify(m_varMap); + + // The manual validation still lies ahead but if we got here without exceptions, + // it means arguments can be accessed via get(). + m_success = true; + + // Manual validation + validateInput(); + } + + void CmdLineParserBase::printHelp() const + { + std::cout << "Usage: " << m_programName << " [options]"; + + for (unsigned int i = 0; i < m_positionalOptions.max_total_count(); ++i) + { + std::string name = + boost::algorithm::to_upper_copy(m_positionalOptions.name_for_position(i)); + std::cout << " " << name; + } + + std::cout << std::endl; + std::cout << m_cmdLineOptions << std::endl; + } + + + void CmdLineParserBase::printVersion() const + { + std::cout << m_programName << " Version: " << m_programVersion << std::endl; + } + + void CmdLineParserBase::printXML() const + { + cmdlineparsing::XMLGenerator generator(m_programCategory, m_programName, m_programDescription, m_programVersion, m_programContributor); + //Generic options doesn't have a corresponding XML entry + if (m_baseOptionsMap.size() - getOptions(OPTION_GROUP_GENERIC).size() == m_additionalOptionsMap.size()){ + generator.setBaseParameters(m_baseOptionsMap); + generator.setAdditionalParameters(m_additionalOptionsMap); + generator.write(std::cout); + } + else { + std::cout << "no information given to create xml" << std::endl; + } + } + + std::unordered_map > CmdLineParserBase::getOptionsAsStringMap() const + { + return m_baseOptionsMap; + } + + + bool CmdLineParserBase::isSet(const std::string& name) const + { + // Help gets special treatment because if the help flag is set, m_success is never + // true since parse() returns prematurely. + if (name == OPTION_HELP) + { + return (m_varMap.count(OPTION_HELP) > 0); + } + else if (name == OPTION_VERSION) + { + return (m_varMap.count(OPTION_VERSION) > 0); + } + else if (name == OPTION_XML) + { + return (m_varMap.count(OPTION_XML) > 0); + } + + if (!m_success) + { + throw AccessException("Parsing wasn't complete. Cannot access arguments via " + "isSet. Make sure there were no exceptions and the help " + "parameter wasn't set."); + } + + return (m_varMap.count(name.c_str()) > 0); + } + + po::options_description& CmdLineParserBase::getOrCreateOptionGroup( + const std::string& optionGroupName) + { + auto iter = m_optionGroupMap.get().find(optionGroupName); + + if (iter == m_optionGroupMap.get().end()) + { + po::options_description newOptionGroup(optionGroupName); + // Can't simply return the newly created option group directly since it's in + // local scope. + // Instead, insert it in the option group map and return its value which is a + // copy of the newly created option group. + return m_optionGroupMap.push_back(OptionGroup(optionGroupName, newOptionGroup)) + .first.get_node()->value().second; + } + + return iter.get_node()->value().second; + } + + void CmdLineParserBase::addInformationForXML(const std::string& name, XMLGenerator::paramType aType, double min /*= 0.0*/, double max /*= 100.0*/, double step /*= 1.0*/) + { + m_additionalOptionsMap.emplace(name, std::make_tuple(aType, min, max, step, std::vector())); + } + + void CmdLineParserBase::addInformationForXML(const std::string& name, XMLGenerator::paramType aType, const std::vector& vectorData) + { + m_additionalOptionsMap.emplace(name, std::make_tuple(aType, 0, 0, 0, vectorData)); + } + + std::unordered_map > CmdLineParserBase::getOptions(const std::string& name) const + { + std::unordered_map > genericOptionsMap; + for (const auto& keyValue : m_baseOptionsMap){ + if (std::get<2>(keyValue.second) == name){ + genericOptionsMap.insert(keyValue); + } + } + return genericOptionsMap; + } + +} \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/main/CmdLineParserBase.h b/utilities/ArgumentParsingLib/main/CmdLineParserBase.h new file mode 100644 index 0000000..d660c77 --- /dev/null +++ b/utilities/ArgumentParsingLib/main/CmdLineParserBase.h @@ -0,0 +1,294 @@ +//------------------------------------------------------------------------ +// Copyright(c) German Cancer Research Center(DKFZ), +// Software Development for Integrated Diagnostics and Therapy (SIDT). +// ALL RIGHTS RESERVED. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notices for more information. +// +// Authors: Sascha Diatschuk, Simon Strubel, Clemens Hentschke, Fabrizio Ciccarese +//------------------------------------------------------------------------ + +#ifndef __CmdLineParserBase_h +#define __CmdLineParserBase_h + +#include +#include +#include +#include +#include +#include + +#include "XMLGenerator.h" + +#include +#include +#include +#include + +namespace cmdlineparsing +{ + /*! + @brief Thrown if accessing parsed parameters fails. + */ + class AccessException : public std::runtime_error + { + public: + AccessException(const std::string& what) : std::runtime_error(what.c_str()) {} + }; + + /*! + @brief Should be thrown if CmdLineParserBase::validateInput() fails. + */ + class InvalidConstraintException : public std::runtime_error + { + public: + InvalidConstraintException(const std::string& what) : + std::runtime_error(what.c_str()) {} + }; + + /*! + @brief Base class for command line parsing including parsing from XML or INI-style + config files. + @details Derive from this class, define your own arguments with + CmdLineParserBase::addOption(), + CmdLineParserBase::addOptionWithDefaultValue() and + CmdLineParserBase::addPositionalOption() and call + CmdLineParserBase::parse() after that to validate them. + + If the parsing completes without exceptions, you can access the parsed + arguments with CmdLineParserBase::isSet() and CmdLineParserBase::get(). + @note You can override CmdLineParserBase::validateInput() to further add custom + validation (e.g. one int parameter mustn't be greater than the other one). + @note You can override CmdLineParserBase::printHelp() to control how the help is + displayed. + @note The default parameters defined in this base class are "--help" (or "-h") + and "--config-file " (or "-c "). If the given config file name + ends in ".xml" it is parsed using cmdlineparsing::parseXML(), otherwise + boost::parse_config_file() is used which expects INI-style parameter + definitions. + @note There is a sample file for each format (.xml and .cfg) in the test data + folder. + */ + + + class CmdLineParserBase + { + public: + + + /*! + @brief Adds the four default parameters "--help" (or "-h"), + "--config-file " (or "-c "), + "--version" (or "-v"), + "--xml" (or "-x") under the group + "Generic Arguments". + */ + CmdLineParserBase(const std::string& programName, const std::string& version = "1.0", const std::string& programDescription="", + const std::string& programContributor="DKFZ", const std::string& programCategory="Generic"); + virtual ~CmdLineParserBase() {} + + + + /*! + @brief Adds a new option to the option group with the given name. Adds a new + option group if it doesn't exist yet. + @param description The description for this parameter that's shown in the help + text. + @param shorthand The shorthand character for this option (e.g. 'h' for + "--help"). + Make sure you don't set the same character twice. + @param required Whether this parameter is mandatory or optional. + @param multitoken Whether this parameter supports an unlimited number of values + (e.g. "-n 1 2 3"). + @param composing Whether this parameter can be composed of the values given in + the command line and those given in the config file (usually + the command line overwrites config file parameter values). + */ + template + void addOption(const std::string& name, const std::string& optionGroupName, + const std::string& description, char shorthand = 0, + bool required = false, bool multitoken = false, + bool composing = false); + + // Despite the similar signature this function needs its own name (instead of + // addOption). Otherwise the default value might be implicitly cast to boolean and + // match one of the other addOption signatures. + /*! + @brief Add option with default value + @param defaultValueRepresentation a string representation of the default value for the output in help + (e.g. std::vector is not streamable, thus defaultValue can't be used in some cases) + @see CmdLineParserBase::addOption() + */ + template + void addOptionWithDefaultValue(const std::string& name, + const std::string& optionGroupName, + const std::string& description, + const T& defaultValue, + const std::string& defaultValueRepresentation = "", + char shorthand = 0, + bool required = false, + bool multitoken = false, bool composing = false); + + void addInformationForXML(const std::string& name, XMLGenerator::paramType aType, double min = 0.0, double max = 100.0, double step = 1.0); + void addInformationForXML(const std::string& name, XMLGenerator::paramType aType, const std::vector& vectorData); + + /*! + @brief Non-generic version for arguments without values (e.g. "help"). + @see CmdLineParserBase::addOption() + */ + void addOption(const std::string& name, const std::string& optionGroupName, + const std::string& description, char shorthand = 0); + + /*! + @brief Adds a positional option with the number of expected values. + @note In order for a positional option to work you need to also add a regular + option of the same name (using CmdLineParserBase::addOption() or + CmdLineParserBase::addOptionWithDefaultValue()). This isn't done for you so + you can decide which group it belongs to. + @note The "usage" line in the "help" text is generated from the positional + arguments. + @param numArgs -1 means an unlimited number of values and only the very last + positional option can have it. + */ + void addPositionalOption(const std::string& name, int numValues); + + /*! + @brief Starts parsing the given command line arguments. + @details Beforehand all custom options must have been defined. + Afterwards, the parsed parameters can be accessed with + CmdLineParserBase::isSet() and CmdLineParserBase::get(). + Also calls CmdLineParserBase::validateInput(). + */ + void parse(int argc, const char* argv[]); + + /*! + @brief Prints the default help to the standard output. The help text is generated + from the option groups, regular options and positional options. + @note Override this function to print your own help text. + */ + virtual void printHelp() const; + + /*! + @brief Prints the default version information to the standard output. + @note Override this function to print your own version information. + */ + virtual void printVersion() const; + + /*! + @brief Prints the default xml information of parameters to the standard output. + @note Override this function to print your own xml information. + */ + virtual void printXML() const; + + /*! + @brief Gets called at the end of CmdLineParserBase::parse(). + @note Override this function to implement your own input validation. E.g. if one + integer parameter mustn't be greater than the other. + */ + virtual void validateInput() const = 0; + + std::unordered_map > getOptionsAsStringMap() const; + + /*! + @brief Access the parsed parameters by name. + @note CmdLineParserBase::parse() must complete successfully before calling this. + @throws cmdlineparsing::AccessException If used before parsing has completed. E.g. + because CmdLineParserBase::parse() threw an + exception or because the help parameter was + set or because CmdLineParserBase::parse() + wasn't called at all. + @throws cmdlineparsing::AccessException If parameter doesn't exist in parameter + map. + */ + template + T get(const std::string& name) const; + + /*! + @brief Check if the parsed parameter with the given name was given. + @note CmdLineParserBase::parse() must complete sucessfully before calling this. + */ + bool isSet(const std::string& name) const; + + const std::string OPTION_GROUP_GENERIC = "Generic Arguments"; + const std::string OPTION_HELP = "help"; + const std::string OPTION_VERSION = "version"; + const std::string OPTION_XML = "xml"; + const std::string OPTION_CONFIG_FILE = "config-file"; + + protected: + // These option groups are protected so you can access them when overriding + // printHelp() + boost::program_options::options_description m_cmdLineOptions, m_configFileOptions; + boost::program_options::positional_options_description m_positionalOptions; + + //Stores basic options for each argument (key is longFlag) (shortFlag, description, optionGroup, default value) + std::unordered_map > m_baseOptionsMap; + //Stores additional options for each argument (key is longFlag) (parameter type, minimum constraint, maximum constraint, step constraint, vector for string vector data) + std::unordered_map > > m_additionalOptionsMap; + + std::string m_programCategory; + std::string m_programName; + std::string m_programDescription; + std::string m_programVersion; + std::string m_programContributor; + + private: + CmdLineParserBase(const CmdLineParserBase&) = delete; + void operator=(const CmdLineParserBase&) = delete; + + // Add option with typed value. Helper function to reduce duplicate code. + template + void addOption(const std::string& name, const std::string& optionGroupName, + const std::string& description, char shorthand, + const boost::program_options::typed_value* value); + + // Returns the option group with the given name or creates a new one and returns it + // if it already exists. + boost::program_options::options_description& getOrCreateOptionGroup( + const std::string& optionGroupName); + + // Generate typed value. Helper function to reduce duplicate code. + template + boost::program_options::typed_value* generateTypedValue(bool required = false, + bool multitoken = false, bool composing = false) const; + + void CmdLineParserBase::findDuplicate(const std::unordered_map >& optionsMap, + const std::string& name, char shorthand) const; + std::unordered_map > getOptions(const std::string& name) const; + boost::program_options::variables_map m_varMap; + + // Stores whether the parsing was successful and arguments can be read via get(). + bool m_success; + + // Multi-index container for the option groups. + // This offers two different indices to the same collection: + // - nameTag: sorted by name (necessary for finding existing option groups) + // - orderTag: sorted by the order they were inserted in (necessary for displaying + // them in the help in the order they were defined) + // Boost's options_description already has a container for option groups, however, + // it doesn't offer enough access to add options to existing groups. + struct nameTag {}; + struct orderTag {}; + typedef std::pair + OptionGroup; + boost::multi_index_container < + OptionGroup, + boost::multi_index::indexed_by < + boost::multi_index::random_access >, + boost::multi_index::ordered_unique < + boost::multi_index::tag, + BOOST_MULTI_INDEX_MEMBER(OptionGroup, std::string, first) + > + > + > + m_optionGroupMap; + }; +} + +#ifndef CMDLINEPARSING_MANUAL_INSTANTIATION +#include "CmdLineParserBase.tpp" +#endif + +#endif \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/main/CmdLineParserBase.tpp b/utilities/ArgumentParsingLib/main/CmdLineParserBase.tpp new file mode 100644 index 0000000..e3fa800 --- /dev/null +++ b/utilities/ArgumentParsingLib/main/CmdLineParserBase.tpp @@ -0,0 +1,100 @@ +#ifndef __CmdLineParserBase_tpp +#define __CmdLineParserBase_tpp + +#include "CmdLineParserBase.h" + +namespace cmdlineparsing +{ + template + void CmdLineParserBase::addOption(const std::string& name, + const std::string& optionGroupName, + const std::string& description, char shorthand, + bool required, bool multitoken, bool composing) + { + addOption(name, optionGroupName, description, shorthand, + generateTypedValue(required, multitoken, composing)); + auto arguments = std::make_tuple(shorthand, description, optionGroupName,"" ); + + findDuplicate(getOptionsAsStringMap(), name, shorthand); + + m_baseOptionsMap[name] = arguments; + } + + template + void CmdLineParserBase::addOption(const std::string& name, + const std::string& optionGroupName, + const std::string& description, char shorthand, + const boost::program_options::typed_value* value) + { + std::string fullName = name; + + if (shorthand != 0) + { + fullName += "," + std::string(1, shorthand); + } + + getOrCreateOptionGroup(optionGroupName).add_options()(fullName.c_str(), value, + description.c_str()); + } + + template + void CmdLineParserBase::addOptionWithDefaultValue(const std::string& name, + const std::string& optionGroupName, const std::string& description, + const T& defaultValue, const std::string& defaultValueRepresentation, char shorthand, bool required, bool multitoken, + bool composing) + { + addOption(name, optionGroupName, description, shorthand, + generateTypedValue(required, multitoken, + composing)->default_value(defaultValue, defaultValueRepresentation)); + + auto arguments = std::make_tuple(shorthand, description, optionGroupName, defaultValueRepresentation); + + findDuplicate(getOptionsAsStringMap(), name, shorthand); + + m_baseOptionsMap[name] = arguments; + } + + template + boost::program_options::typed_value* CmdLineParserBase::generateTypedValue( + bool required, bool multitoken, bool composing) const + { + boost::program_options::typed_value* value = boost::program_options::value(); + + if (required) + { + value = value->required(); + } + + if (multitoken) + { + value = value->multitoken(); + } + + if (composing) + { + value = value->composing(); + } + + return value; + } + + template + T CmdLineParserBase::get(const std::string& name) const + { + if (!m_success) + { + throw AccessException("Parsing wasn't complete. Cannot access arguments via " + "get. Make sure there were no exceptions and the help " + "parameter wasn't set."); + } + + if (!m_varMap.count(name)) + { + throw AccessException("Option " + name + " not found in parsed arguments."); + } + + return m_varMap[name].as(); + } +} + +#endif \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/main/XMLGenerator.cpp b/utilities/ArgumentParsingLib/main/XMLGenerator.cpp new file mode 100644 index 0000000..0dc8fa7 --- /dev/null +++ b/utilities/ArgumentParsingLib/main/XMLGenerator.cpp @@ -0,0 +1,197 @@ +//------------------------------------------------------------------------ +// Copyright(c) German Cancer Research Center(DKFZ), +// Software Development for Integrated Diagnostics and Therapy (SIDT). +// ALL RIGHTS RESERVED. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notices for more information. +// +// Authors: Simon Strubel, Clemens Hentschke +//------------------------------------------------------------------------ + +#include "XMLGenerator.h" + +namespace cmdlineparsing +{ + + XMLGenerator::XMLGenerator(const std::string& programCategory, const std::string& programName, + const std::string& programDescription, const std::string& programVersion, const std::string& programContributor) : m_programCategory(programCategory), m_programName(programName), m_programDescription(programDescription), m_programVersion(programVersion), m_programContributor(programContributor) + { + } + + void XMLGenerator::write(std::ostream& stream) const + { + boost::property_tree::ptree root; + addProgrammDesc(root, m_programCategory, m_programName, m_programDescription, m_programVersion, m_programContributor); + + std::vector groups = getOptionGroups(); + + for (const auto& aGroup : groups) + { + boost::property_tree::ptree& option = root.add("executable.parameters", ""); + addGroupTag(option, aGroup); + for (const auto& keyValue : m_baseParameters){ + if (aGroup == std::get<2>(keyValue.second)) + { + if (m_additionalParameters.find(keyValue.first) != m_additionalParameters.end()){ + addParameterTags(option, keyValue.first, keyValue.second, m_additionalParameters.at(keyValue.first)); + } + } + } + } + boost::property_tree::write_xml(stream, root, boost::property_tree::xml_writer_make_settings(' ', 4)); + } + + std::vector XMLGenerator::getOptionGroups() const + { + std::vector optionGroup; + + for (const auto& keyValue : m_baseParameters) + { + if (std::get<2>(keyValue.second) != "Generic Arguments") + { + optionGroup.push_back(std::get<2>(keyValue.second)); + } + } + //remove duplicates + optionGroup.erase(unique(optionGroup.begin(), optionGroup.end()), optionGroup.end()); + return optionGroup; + } + + void XMLGenerator::addParameterTags(boost::property_tree::ptree& node, const std::string longFlag, const std::tuple& baseParameterInfo, const std::tuple >& additionalParameterInfo) const + { + char shortFlag; + std::string description; + std::string optionGroup; + std::string defaultValue; + paramType aType; + double minConstraint; + double maxConstraint; + double stepConstraint; + std::vector vectorData; + std::tie(shortFlag, description, optionGroup, defaultValue) = baseParameterInfo; + std::tie(aType, minConstraint, maxConstraint, stepConstraint, vectorData) = additionalParameterInfo; + + boost::property_tree::ptree subnode; + switch (aType){ + case paramType::BOOLEAN: + subnode = createParameterTagWithDefaultValue(longFlag, shortFlag, description, defaultValue); + node.add_child("boolean", subnode); + break; + case paramType::DOUBLE: + subnode = createParameterTagWithDefaultValue(longFlag, shortFlag, description, defaultValue); + addConstraints(subnode, minConstraint, maxConstraint, stepConstraint); + node.add_child("double", subnode); + break; + case paramType::INPUT: + subnode = createParameterTagWithDefaultValue(longFlag, shortFlag, description, defaultValue); + addIOTag(subnode, "input", vectorData); + node.add_child("file", subnode); + break; + case paramType::INTEGER: + subnode = createParameterTagWithDefaultValue(longFlag, shortFlag, description, defaultValue); + addConstraints(subnode, minConstraint, maxConstraint, stepConstraint, true); + node.add_child("integer", subnode); + break; + case paramType::OUTPUT: + subnode = createParameterTagWithDefaultValue(longFlag, shortFlag, description, defaultValue); + addIOTag(subnode, "output", vectorData); + node.add_child("file", subnode); + break; + case paramType::STRING: + subnode = createParameterTagWithDefaultValue( longFlag, shortFlag, description, defaultValue); + node.add_child("string", subnode); + break; + case paramType::STRINGENUMERATION: + subnode = createParameterTagWithDefaultValue(longFlag, shortFlag, description, defaultValue); + addEnums(subnode, vectorData); + node.add_child("string-enumeration", subnode); + break; + case paramType::STRINGVECTOR: + subnode = createParameterTagWithDefaultValue(longFlag, shortFlag, description, defaultValue); + node.add_child("string-vector", subnode); + break; + default: + throw std::runtime_error("unknown parameter type"); + break; + } + } + + void XMLGenerator::addProgrammDesc(boost::property_tree::ptree& node, const std::string& category, const std::string& name, + const std::string& description, const std::string& version, const std::string& contributor) const + { + node.add("executable.category", category); + node.add("executable.title", name); + node.add("executable.description", description); + node.add("executable.version", version); + node.add("executable.contributor", contributor); + } + + void XMLGenerator::addGroupTag(boost::property_tree::ptree& node, const std::string& label) const + { + node.add("label", label); + node.add("description", label); + } + + boost::property_tree::ptree XMLGenerator::createParameterTagWithDefaultValue(const std::string& longflag, + char flag, const std::string& description, const std::string& defaultValue) const + { + boost::property_tree::ptree subnode; + subnode.add("name", longflag); + subnode.add("flag", flag); + subnode.add("longflag", longflag); + subnode.add("description", description); + subnode.add("label", longflag); + if (!defaultValue.empty()){ + subnode.put("default", defaultValue); + } + return subnode; + } + + void XMLGenerator::addIOTag(boost::property_tree::ptree& node, const std::string& channel, const std::vector& desiredFileExtensions) const + { + std::string extensions; + for (const auto& extension : desiredFileExtensions){ + extensions += "." + extension; + extensions += ","; + } + extensions.pop_back(); + + node.add(".fileExtensions", extensions); + node.add("channel", channel); + } + + void XMLGenerator::addConstraints(boost::property_tree::ptree& node, double minimum, double maximum, double step, bool castToInt) const + { + boost::property_tree::ptree& constraints = node.add("constraints", ""); + if (castToInt){ + constraints.add("minimum", static_cast(minimum)); + constraints.add("maximum", static_cast(maximum)); + constraints.add("step", static_cast(step)); + } + else { + constraints.add("minimum", minimum); + constraints.add("maximum", maximum); + constraints.add("step", step); + } + } + + void XMLGenerator::addEnums(boost::property_tree::ptree& node, std::vector data) const + { + for (const auto& element : data){ + node.add("element", element); + } + } + + void XMLGenerator::setBaseParameters(const std::unordered_map >& parameters) + { + m_baseParameters = parameters; + } + + void XMLGenerator::setAdditionalParameters(const std::unordered_map > >& parameters) + { + m_additionalParameters = parameters; + } + +} diff --git a/utilities/ArgumentParsingLib/main/XMLGenerator.h b/utilities/ArgumentParsingLib/main/XMLGenerator.h new file mode 100644 index 0000000..1d165ee --- /dev/null +++ b/utilities/ArgumentParsingLib/main/XMLGenerator.h @@ -0,0 +1,103 @@ +//------------------------------------------------------------------------ +// Copyright(c) German Cancer Research Center(DKFZ), +// Software Development for Integrated Diagnostics and Therapy (SIDT). +// ALL RIGHTS RESERVED. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notices for more information. +// +// Authors: Simon Strubel, Clemens Hentschke +//------------------------------------------------------------------------ + +#ifndef __generateXML_h +#define __generateXML_h + +#include +#include + +#include +#include +#include +#include + +namespace cmdlineparsing +{ + + class XMLGenerator + { + + public: + enum class paramType { INPUT, OUTPUT, BOOLEAN, STRING, INTEGER, DOUBLE, STRINGVECTOR, STRINGENUMERATION }; + + XMLGenerator(const std::string& programCategory, const std::string& programName, + const std::string& programDescription, const std::string& programVersion = "1.0", const std::string& programContributor = "DKFZ"); + /*! + @brief writes the xml to the stream + */ + void write(std::ostream& stream) const; + /*! + @brief sets the basic information parameter map (i.e. that are mandatory) + @param parameters (key: longFlag) (values: shortFlag, description, optionGroup, default value) + @sa CmdLineParserBase + */ + void setBaseParameters(const std::unordered_map >& parameters); + /*! + @brief sets the additional information parameter map (i.e. only needed for XML info) + @param parameters (key: longFlag) (value: parameter type, minimum constraint, maximum constraint, step constraint, vector for string vector data) + @sa CmdLineParserBase + */ + void setAdditionalParameters(const std::unordered_map > >& parameters); + + private: + /*! + @brief finds the different groups + @return a vector of all found groups + */ + std::vector getOptionGroups() const; + /*! + @brief adds a generic parameter tag to the node + @param longflag (e.g. foo) + @param baseParameterInfo basic information like description and default value + @param additionalParameterInfo information for xml like type, constraints + @sa setBaseParameters + */ + void addParameterTags(boost::property_tree::ptree& node, const std::string longFlag, const std::tuple& baseParameterInfo, const std::tuple >& additionalParameterInfo) const; + /*! + @brief adds program description to the node + */ + void addProgrammDesc(boost::property_tree::ptree& node, const std::string& category, const std::string& name, + const std::string& description, const std::string& version, const std::string& contributor) const; + /*! + @brief adds a group tag to the node + @detail required for different groups + */ + void addGroupTag(boost::property_tree::ptree& node, const std::string& label) const; + /*! + @brief creates specific parameter tags (int/double/string-enum etc.) + */ + boost::property_tree::ptree createParameterTagWithDefaultValue(const std::string& longflag, + char flag, const std::string& description, const std::string& defaultValue) const; + /*! + @brief adds IO information for file parameter tags + @param channel either input or output + @param desiredFileExtensions a list of file extensions, e.g. {"txt", "xml", "jpg"} + */ + void addIOTag(boost::property_tree::ptree& node, const std::string& channel, const std::vector& desiredFileExtensions) const; + /*! + @brief adds constraints to the node + @param castToInt the double values are cast to int (for integer parameters) + */ + void addConstraints(boost::property_tree::ptree& node, double minConstraint, double maxConstraint, double stepConstraint, bool castToInt = false) const; + /*! + @brief adds enums to the node + */ + void addEnums(boost::property_tree::ptree& node, std::vector vectorData) const; + + std::unordered_map > m_baseParameters; + std::unordered_map > > m_additionalParameters; + + std::string m_programName, m_programCategory, m_programDescription, m_programVersion, m_programContributor; + }; +} +#endif \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/main/parseXML.cpp b/utilities/ArgumentParsingLib/main/parseXML.cpp new file mode 100644 index 0000000..b0b9806 --- /dev/null +++ b/utilities/ArgumentParsingLib/main/parseXML.cpp @@ -0,0 +1,82 @@ +//------------------------------------------------------------------------ +// Copyright(c) German Cancer Research Center(DKFZ), +// Software Development for Integrated Diagnostics and Therapy (SIDT). +// ALL RIGHTS RESERVED. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notices for more information. +// +// Author: Sascha Diatschuk +//------------------------------------------------------------------------ + +#include +#include "parseXML.h" + +#include + +namespace po = boost::program_options; +namespace pt = boost::property_tree; + +namespace cmdlineparsing +{ + po::parsed_options parseXML(const std::string& fileName, po::options_description& desc) + { + // Taken from po::parse_config_file() + std::set allowedOptions; + const std::vector >& options = + desc.options(); + + for (unsigned i = 0; i < options.size(); ++i) + { + const po::option_description& d = *options[i]; + + if (d.long_name().empty()) + { + boost::throw_exception(po::error("abbreviated option names are not " + "permitted in options configuration " + "files")); + } + + allowedOptions.insert(d.long_name()); + } + + // Load file and parse XML tree + po::parsed_options result(&desc); + pt::ptree tree; + std::ifstream fileStream(fileName.c_str()); + + if (!fileStream) + { + boost::throw_exception(po::reading_file(fileName.c_str())); + } + + pt::read_xml(fileStream, tree); + + // Assign parameters + for (pt::ptree::value_type& paramContainer : tree.get_child("Parameters")) + { + std::string key; + std::vector value; + + for (pt::ptree::value_type& param : paramContainer.second) + { + if (param.first == "Name") + { + key = param.second.data(); + } + else if (param.first == "Value") + { + value.push_back(param.second.data()); + } + } + + if (!key.empty() && allowedOptions.find(key) != allowedOptions.end()) + { + result.options.push_back(po::option(key, value)); + } + } + + return result; + } +} \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/main/parseXML.h b/utilities/ArgumentParsingLib/main/parseXML.h new file mode 100644 index 0000000..9f831e9 --- /dev/null +++ b/utilities/ArgumentParsingLib/main/parseXML.h @@ -0,0 +1,33 @@ +//------------------------------------------------------------------------ +// Copyright(c) German Cancer Research Center(DKFZ), +// Software Development for Integrated Diagnostics and Therapy (SIDT). +// ALL RIGHTS RESERVED. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notices for more information. +// +// Author: Sascha Diatschuk +//------------------------------------------------------------------------ + +#ifndef __parseXML_h +#define __parseXML_h + +#include + +namespace cmdlineparsing +{ + /*! + @brief Parses the given XML file with the given parameter rule set. + @param ruleSet Option container (possibly containing multiple option groups) defining + the valid arguments and their expected values. + @throw boost::program_options::error If the rule set contains an option that has no + name or long name (i.e. only the one-letter + shortcut). + @throw boost::program_options::reading_file If the XML file could not be read. + */ + boost::program_options::parsed_options parseXML(const std::string& fileName, + boost::program_options::options_description& ruleSet); +} + +#endif \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/test/CMakeLists.txt b/utilities/ArgumentParsingLib/test/CMakeLists.txt new file mode 100644 index 0000000..f1db183 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/CMakeLists.txt @@ -0,0 +1,74 @@ +if (BUILD_TESTS) + find_package(Threads REQUIRED) # Necessary under Linux. Has no effect on Windows. + + find_package(GTest) + if(NOT GTEST_FOUND) + message(STATUS "GTest will be automatically downloaded and built.") + include(ExternalProject) + + set(GTEST_SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src") + set(GTEST_BUILD_DIR "${CMAKE_BINARY_DIR}/googletest-build") + set(GTEST_CMAKE_DIR "${CMAKE_BINARY_DIR}/googletest-cmake") + + if (CMAKE_VERSION VERSION_LESS 3.2) + set(UPDATE_DISCONNECTED_IF_AVAILABLE "") + else() + set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") + endif() + + include(DownloadProject.cmake) + download_project(PROJ googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + ${UPDATE_DISCONNECTED_IF_AVAILABLE} + ) + + # Prevent GoogleTest from overriding our compiler/linker options + # when building with Visual Studio + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + SET(BUILD_GTEST ON CACHE BOOL "" FORCE) + SET(BUILD_GMOCK OFF) + + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) + + set(GTEST_INCLUDE_DIR "${GTEST_SOURCE_DIR}/googletest/include") + link_directories("${GTEST_BUILD_DIR}/googletest") # Has to be called before add_executable + + set(GTEST_BOTH_LIBRARIES "${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}") + list(APPEND GTEST_BOTH_LIBRARIES "${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + endif() + + set(ABS_MAIN_DIR "${PROJECT_SOURCE_DIR}/${MAIN_DIR}") + include_directories(${GTEST_INCLUDE_DIR} ${ABS_MAIN_DIR}) # Has to be called before add_executable + + set(TEST_PROJECT_NAME "${PROJECT_NAME}Tests") + set(CPP_FILES + ${ABS_MAIN_DIR}/parseXML.cpp + ${ABS_MAIN_DIR}/CmdLineParserBase.cpp + ${ABS_MAIN_DIR}/XMLGenerator.cpp + parseXMLTest.cpp + CmdLineParserBaseTest.cpp + XMLGeneratorTest.cpp + ) + + add_executable(${TEST_PROJECT_NAME} ${CPP_FILES}) + target_link_libraries(${TEST_PROJECT_NAME} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + + if(NOT GTEST_FOUND) + add_dependencies(${TEST_PROJECT_NAME} gtest) # Necessary so the build waits for gtest + endif() + + # MSVC automatically links to boost + if(NOT MSVC) + target_link_libraries(${TEST_PROJECT_NAME} ${Boost_LIBRARIES}) + endif() + + # Copy test data files to build dir + add_custom_command(TARGET ${TEST_PROJECT_NAME} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/data ${CMAKE_CURRENT_BINARY_DIR}) + + # Enable C++11 support (doesn't work with MSVC) + set_property(TARGET ${TEST_PROJECT_NAME} PROPERTY CXX_STANDARD 11) + set_property(TARGET ${TEST_PROJECT_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) +endif() \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/test/CmdLineParserBaseTest.cpp b/utilities/ArgumentParsingLib/test/CmdLineParserBaseTest.cpp new file mode 100644 index 0000000..4535bab --- /dev/null +++ b/utilities/ArgumentParsingLib/test/CmdLineParserBaseTest.cpp @@ -0,0 +1,449 @@ +//------------------------------------------------------------------------ +// Copyright(c) German Cancer Research Center(DKFZ), +// Software Development for Integrated Diagnostics and Therapy (SIDT). +// ALL RIGHTS RESERVED. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notices for more information. +// +// Authors: Sascha Diatschuk, Simon Strubel, Clemens Hentschke, Fabrizio Ciccarese +// +//------------------------------------------------------------------------ + +#include +#include "CmdLineParserBase.h" +#include "XMLGenerator.h" + + +// Helper function to facilitate passing a dynamic string array as const char** +std::vector stdStringVec2CStringVec(const std::vector& + stringList) +{ + std::vector pointers(stringList.size()); + std::transform(stringList.begin(), stringList.end(), pointers.begin(), + [](const std::string & s) + { + return s.c_str(); + }); + pointers.push_back(0); + + return pointers; +} + +// Since CmdLineParserBase is abstract, we need to sub-class it in order to test it. +class CmdLineParserBaseTest : + public cmdlineparsing::CmdLineParserBase, + public testing::Test +{ +public: + CmdLineParserBaseTest() : CmdLineParserBase("CmdLineParserBaseTest","1.2","description","contributor","category") + { + // Capture stdout + m_stdCout = std::cout.rdbuf(); + std::cout.rdbuf(m_strCout.rdbuf()); + } + ~CmdLineParserBaseTest() + { + // Change stdout back to normal + std::cout.rdbuf(m_stdCout); + } + + void validateInput() const {} + + std::ostringstream m_strCout; // Temporary output + std::streambuf* m_stdCout; // stdout +}; + +// Since CmdLineParserBase is abstract, we need to sub-class it in order to test it. +class CmdLineParserBaseDefaultTest : + public cmdlineparsing::CmdLineParserBase, + public testing::Test +{ +public: + CmdLineParserBaseDefaultTest() : CmdLineParserBase("CmdLineParserBaseTest") + { + // Capture stdout + m_stdCout = std::cout.rdbuf(); + std::cout.rdbuf(m_strCout.rdbuf()); + } + ~CmdLineParserBaseDefaultTest() + { + // Change stdout back to normal + std::cout.rdbuf(m_stdCout); + } + + void validateInput() const {} + + std::ostringstream m_strCout; // Temporary output + std::streambuf* m_stdCout; // stdout +}; + +TEST_F(CmdLineParserBaseTest, BeforeParse) +{ + // These functions all require parse() to be called before-hand and should all fail + EXPECT_THROW(isSet(std::string()), cmdlineparsing::AccessException); + EXPECT_THROW(isSet("test"), cmdlineparsing::AccessException); + EXPECT_THROW(isSet("config-file"), cmdlineparsing::AccessException); + EXPECT_THROW(get(std::string()), cmdlineparsing::AccessException); + EXPECT_THROW(get("test"), cmdlineparsing::AccessException); + EXPECT_THROW(get("help"), cmdlineparsing::AccessException); + EXPECT_THROW(get("version"), cmdlineparsing::AccessException); + EXPECT_THROW(get("xml"), cmdlineparsing::AccessException); + EXPECT_THROW(get("config-file"), cmdlineparsing::AccessException); +} + +TEST_F(CmdLineParserBaseDefaultTest, ConstructorDefault) +{ + EXPECT_EQ("CmdLineParserBaseTest", m_programName); + EXPECT_EQ("1.0", m_programVersion); + EXPECT_EQ("Generic", m_programCategory); + EXPECT_EQ("", m_programDescription); + EXPECT_EQ("DKFZ", m_programContributor); +} + +TEST_F(CmdLineParserBaseTest, Constructor) +{ + EXPECT_EQ("CmdLineParserBaseTest", m_programName); + EXPECT_EQ("1.2", m_programVersion); + EXPECT_EQ("category", m_programCategory); + EXPECT_EQ("description", m_programDescription); + EXPECT_EQ("contributor", m_programContributor); +} + +TEST_F(CmdLineParserBaseTest, PrintBasicHelp) +{ + printHelp(); + std::string expected = "Usage: " + m_programName + " [options]\n\n"; + EXPECT_EQ(expected, m_strCout.str()); +} + +TEST_F(CmdLineParserBaseTest, PrintBasicVersion) +{ + printVersion(); + std::string expected = m_programName + " Version: " + m_programVersion + "\n"; + EXPECT_EQ(expected, m_strCout.str()); +} + +TEST_F(CmdLineParserBaseTest, AddOption) +{ + addOption(std::string(), std::string(), std::string()); + parse(0, nullptr); + EXPECT_EQ(5, this->m_cmdLineOptions.options().size()); +} + +TEST_F(CmdLineParserBaseTest, AddTemplatedOption) +{ + addOption("test", "test", "test"); + parse(0, nullptr); + EXPECT_EQ(5, this->m_cmdLineOptions.options().size()); +} + +TEST_F(CmdLineParserBaseTest, AddOptionWithDefaultValue) +{ + addOptionWithDefaultValue("test", "test", "test", "test"); + parse(0, nullptr); + EXPECT_EQ(5, this->m_cmdLineOptions.options().size()); +} + +TEST_F(CmdLineParserBaseTest, AddInformationForXMLSimple) +{ + addOption("test", "test", "test"); + addInformationForXML("test", cmdlineparsing::XMLGenerator::paramType::STRING); + parse(0, nullptr); + EXPECT_EQ(1, this->m_additionalOptionsMap.size()); + EXPECT_EQ(cmdlineparsing::XMLGenerator::paramType::STRING, std::get<0>(this->m_additionalOptionsMap.at("test"))); +} + +TEST_F(CmdLineParserBaseTest, AddInformationForXMLMinMaxStep) +{ + addOption("test", "test", "test"); + addInformationForXML("test", cmdlineparsing::XMLGenerator::paramType::DOUBLE, 1, 40,2); + parse(0, nullptr); + EXPECT_EQ(1, this->m_additionalOptionsMap.size()); + EXPECT_EQ(1, std::get<1>(this->m_additionalOptionsMap.at("test"))); + EXPECT_EQ(40, std::get<2>(this->m_additionalOptionsMap.at("test"))); + EXPECT_EQ(2, std::get<3>(this->m_additionalOptionsMap.at("test"))); +} + +TEST_F(CmdLineParserBaseTest, AddInformationForXMLVector) +{ + addOption("test", "test", "test"); + addInformationForXML("test", cmdlineparsing::XMLGenerator::paramType::STRINGENUMERATION, std::vector{"eins", "zwei", "drei"}); + parse(0, nullptr); + EXPECT_EQ(1, this->m_additionalOptionsMap.size()); + std::vector testVector{ "eins", "zwei", "drei" }; + EXPECT_EQ(testVector, std::get<4>(this->m_additionalOptionsMap.at("test"))); +} + +TEST_F(CmdLineParserBaseTest, AddOptionWithDefaultValueVector) +{ + std::vector defaultValue = { "first", "second" }; + addOptionWithDefaultValue >("test", "test", "test", defaultValue, + defaultValue.at(0) + " " + defaultValue.at(1)); + parse(0, nullptr); + EXPECT_EQ(5, this->m_cmdLineOptions.options().size()); +} + +TEST_F(CmdLineParserBaseTest, ParseInvalidParameter) +{ + std::vector argList = { "", "--test" }; + ASSERT_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0]), + boost::program_options::unknown_option); +} + +TEST_F(CmdLineParserBaseTest, ParseHelp) +{ + std::vector argList = { "", "--" + OPTION_HELP }; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); +} + +TEST_F(CmdLineParserBaseTest, ParseVersion) +{ + std::vector argList = { "", "--" + OPTION_VERSION }; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); +} + +TEST_F(CmdLineParserBaseTest, ParseXML) +{ + addOptionWithDefaultValue("test", "test", "test", "test"); + addInformationForXML("test", cmdlineparsing::XMLGenerator::paramType::STRING); + std::vector argList = { "", "--" + OPTION_XML }; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); +} + +TEST_F(CmdLineParserBaseTest, ParseXMLNoInfo) +{ + addOptionWithDefaultValue("test", "test", "test", "test"); + std::vector argList = { "", "--" + OPTION_XML }; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); +} + +TEST_F(CmdLineParserBaseTest, ParseShorthand) +{ + std::vector argList = { "", "-h" }; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); +} + +TEST_F(CmdLineParserBaseTest, ParseCfgFileUnary) +{ + std::vector argList = { "", "--" + OPTION_CONFIG_FILE }; + ASSERT_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0]), + boost::program_options::invalid_command_line_syntax); +} + +TEST_F(CmdLineParserBaseTest, ParseCfgFileSimple) +{ + std::vector argList = { "", "--" + OPTION_CONFIG_FILE, "simple.xml" }; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); +} + +TEST_F(CmdLineParserBaseTest, ParseCfgFileComplex) +{ + const std::string OPTION_MULTITOKEN_NUMBERS = "multitoken-numbers"; + const std::string OPTION_COMPOSING_NUMBERS = "composing-numbers"; + const std::string OPTION_FLAG = "flag"; + const std::string OPTION_OUTPUT_FILE = "output-file"; + const int cmdComposedNumbers[2] = { 100, 200 }; + + addOption >(OPTION_MULTITOKEN_NUMBERS, "", "", 0, false, true); + addOption >(OPTION_COMPOSING_NUMBERS, "", "", 0, false, true, true); + addOption(OPTION_FLAG, "", ""); + addOption(OPTION_OUTPUT_FILE, "", ""); + + std::string num1 = std::to_string(cmdComposedNumbers[0]); + std::string num2 = std::to_string(cmdComposedNumbers[1]); + + std::vector argList = + { + "", "--" + OPTION_CONFIG_FILE, "sample.xml", "--" + OPTION_COMPOSING_NUMBERS, + std::to_string(cmdComposedNumbers[0]), std::to_string(cmdComposedNumbers[1]) + }; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); + + std::vector multitokenNumbers = get >("multitoken-numbers"); + EXPECT_EQ(2, multitokenNumbers.size()); + EXPECT_EQ(99, multitokenNumbers[0]); + EXPECT_EQ(999, multitokenNumbers[1]); + + std::vector composingNumbers = get >("composing-numbers"); + EXPECT_EQ(5, composingNumbers.size()); + EXPECT_EQ(cmdComposedNumbers[0], composingNumbers[0]); + EXPECT_EQ(cmdComposedNumbers[1], composingNumbers[1]); + EXPECT_EQ(10, composingNumbers[2]); + EXPECT_EQ(20, composingNumbers[3]); + EXPECT_EQ(30, composingNumbers[4]); + + EXPECT_TRUE(isSet("flag")); + + EXPECT_EQ("out.txt", get("output-file")); +} + +TEST_F(CmdLineParserBaseTest, ParseRequiredMissing) +{ + const std::string OPTION_REQUIRED = "input-file"; + + addOption(OPTION_REQUIRED, "", "", 0, true); + + std::vector argList = { "" }; + ASSERT_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0]), + boost::program_options::required_option); +} + +TEST_F(CmdLineParserBaseTest, ParseInt) +{ + const std::string OPTION_NUMBER = "number"; + const int number = 3; + + addOption(OPTION_NUMBER, "", ""); + + std::vector argList = {"", "--" + OPTION_NUMBER, std::to_string(number)}; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); + + EXPECT_EQ(number, get(OPTION_NUMBER)); +} + +TEST_F(CmdLineParserBaseTest, ParseBool) +{ + const std::string OPTION_FLAG = "flag"; + + addOption(OPTION_FLAG, "", ""); + + std::vector argList = { "", "--" + OPTION_FLAG }; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); + + EXPECT_TRUE(isSet(OPTION_FLAG)); +} + +TEST_F(CmdLineParserBaseTest, ParseDouble) +{ + const std::string OPTION_DOUBLE = "double"; + const double d = 3.14159266e114; + + addOption(OPTION_DOUBLE, "", ""); + + std::vector argList = { "", "--" + OPTION_DOUBLE, std::to_string(d) }; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); + + EXPECT_DOUBLE_EQ(d, get(OPTION_DOUBLE)); +} + +TEST_F(CmdLineParserBaseTest, ParseString) +{ + const std::string OPTION_STRING = "string"; + const std::string s = "test"; + + addOption(OPTION_STRING, "", ""); + + std::vector argList = { "", "--" + OPTION_STRING, s }; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); + + EXPECT_EQ(s, get(OPTION_STRING)); +} + +TEST_F(CmdLineParserBaseTest, ParseComposingNumbers) +{ + const std::string OPTION_COMPOSING_NUMBERS = "composing"; + std::vector numbers = { 1, 2, 3 }; + + addOption >(OPTION_COMPOSING_NUMBERS, "", "", 0, false, true); + + std::vector argList = { "", "--" + OPTION_COMPOSING_NUMBERS }; + + for (int i : numbers) + { + argList.push_back(std::to_string(i)); + } + + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); + + std::vector results = get >(OPTION_COMPOSING_NUMBERS); + EXPECT_EQ(numbers.size(), results.size()); + + for (int i = 0; i < numbers.size(); ++i) + { + EXPECT_EQ(numbers[i], results[i]); + } +} + +TEST_F(CmdLineParserBaseTest, ParseDefaultValue) +{ + const std::string OPTION_NUMBER = "number"; + const int defaultValue = 12; + + addOptionWithDefaultValue(OPTION_NUMBER, "", "", defaultValue); + + std::vector argList = { "" }; + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); + + EXPECT_EQ(defaultValue, get(OPTION_NUMBER)); +} + +TEST_F(CmdLineParserBaseTest, ParsePositionalOptions) +{ + const std::string OPTION_OUTPUT_FILE = "output-file"; + const std::string OPTION_INPUT_FILES = "input-files"; + const std::string outputFile = "out.txt"; + const std::vector inputFiles = { "a.txt", "b.txt", "c.txt" }; + + addOption(OPTION_OUTPUT_FILE, "", "", 0, true); + addOption >(OPTION_INPUT_FILES, "", "", 0, true, true); + + addPositionalOption(OPTION_OUTPUT_FILE, 1); + addPositionalOption(OPTION_INPUT_FILES, -1); + + std::vector argList = { "", outputFile }; + + for (const std::string& s : inputFiles) + { + argList.push_back(s); + } + + ASSERT_NO_THROW(parse(static_cast(argList.size()), + &stdStringVec2CStringVec(argList)[0])); + + EXPECT_EQ(outputFile, get(OPTION_OUTPUT_FILE)); + + std::vector results = get >(OPTION_INPUT_FILES); + EXPECT_EQ(inputFiles.size(), results.size()); + + for (int i = 0; i < results.size(); ++i) + { + EXPECT_EQ(inputFiles[i], results[i]); + } +} + +TEST_F(CmdLineParserBaseTest, AddDuplicatedOptions) +{ + ASSERT_THROW(addOption("help", "", "", 'i'), boost::program_options::duplicate_option_error); + ASSERT_THROW(addOption("sdsa", "", "", 'h'), boost::program_options::duplicate_option_error); + ASSERT_NO_THROW(addOption("something", "", "", 'y')); + ASSERT_THROW(addOption("something", "", "", 's'), boost::program_options::duplicate_option_error); + ASSERT_THROW(addOption("hhh", "", "", 'y'), boost::program_options::duplicate_option_error); + ASSERT_NO_THROW(addOption("noShorthand", "", "")); + ASSERT_NO_THROW(addOption("noShorthand2", "", "")); + ASSERT_THROW(addOption("option_name", "", "", 'y', false, true, true), boost::program_options::duplicate_option_error); + ASSERT_NO_THROW(addOption>("option_name2", "", "", 'm', false, true, true)); + ASSERT_THROW(addOption("option_name2", "", "", 'f', false, true, true), boost::program_options::duplicate_option_error); + ASSERT_THROW(addOption("option_name2", "", "", false, true, true), boost::program_options::duplicate_option_error); + ASSERT_THROW(addOptionWithDefaultValue("help", "", "", "", "", 'n', false, true, true), + boost::program_options::duplicate_option_error); + ASSERT_THROW(addOptionWithDefaultValue("help", "", "", "", "", false, true, true), + boost::program_options::duplicate_option_error); +} diff --git a/utilities/ArgumentParsingLib/test/DownloadProject.CMakeLists.cmake.in b/utilities/ArgumentParsingLib/test/DownloadProject.CMakeLists.cmake.in new file mode 100644 index 0000000..3af7ee0 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/DownloadProject.CMakeLists.cmake.in @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 2.8.2) + +project(${DL_ARGS_PROJ}-download NONE) + +include(ExternalProject) +ExternalProject_Add(${DL_ARGS_PROJ}-download + ${DL_ARGS_UNPARSED_ARGUMENTS} + SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" + BINARY_DIR "${DL_ARGS_BINARY_DIR}" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/test/DownloadProject.cmake b/utilities/ArgumentParsingLib/test/DownloadProject.cmake new file mode 100644 index 0000000..3574991 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/DownloadProject.cmake @@ -0,0 +1,144 @@ + +# MODULE: DownloadProject +# +# PROVIDES: +# download_project( PROJ projectName +# [PREFIX prefixDir] +# [DOWNLOAD_DIR downloadDir] +# [SOURCE_DIR srcDir] +# [BINARY_DIR binDir] +# [QUIET] +# ... +# ) +# +# Provides the ability to download and unpack a tarball, zip file, git repository, +# etc. at configure time (i.e. when the cmake command is run). How the downloaded +# and unpacked contents are used is up to the caller, but the motivating case is +# to download source code which can then be included directly in the build with +# add_subdirectory() after the call to download_project(). Source and build +# directories are set up with this in mind. +# +# The PROJ argument is required. The projectName value will be used to construct +# the following variables upon exit (obviously replace projectName with its actual +# value): +# +# projectName_SOURCE_DIR +# projectName_BINARY_DIR +# +# The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically +# need to be provided. They can be specified if you want the downloaded source +# and build directories to be located in a specific place. The contents of +# projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the +# locations used whether you provide SOURCE_DIR/BINARY_DIR or not. +# +# The DOWNLOAD_DIR argument does not normally need to be set. It controls the +# location of the temporary CMake build used to perform the download. +# +# The PREFIX argument can be provided to change the base location of the default +# values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments +# are provided, then PREFIX will have no effect. The default value for PREFIX is +# CMAKE_BINARY_DIR. +# +# The QUIET option can be given if you do not want to show the output associated +# with downloading the specified project. +# +# In addition to the above, any other options are passed through unmodified to +# ExternalProject_Add() to perform the actual download, patch and update steps. +# The following ExternalProject_Add() options are explicitly prohibited (they +# are reserved for use by the download_project() command): +# +# CONFIGURE_COMMAND +# BUILD_COMMAND +# INSTALL_COMMAND +# TEST_COMMAND +# +# Only those ExternalProject_Add() arguments which relate to downloading, patching +# and updating of the project sources are intended to be used. Also note that at +# least one set of download-related arguments are required. +# +# If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to +# prevent a check at the remote end for changes every time CMake is run +# after the first successful download. See the documentation of the ExternalProject +# module for more information. It is likely you will want to use this option if it +# is available to you. +# +# EXAMPLE USAGE: +# +# include(download_project.cmake) +# download_project(PROJ googletest +# GIT_REPOSITORY https://github.com/google/googletest.git +# GIT_TAG master +# UPDATE_DISCONNECTED 1 +# QUIET +# ) +# +# add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) +# +#======================================================================================== + + +set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}") + +include(CMakeParseArguments) + +function(download_project) + + set(options QUIET) + set(oneValueArgs + PROJ + PREFIX + DOWNLOAD_DIR + SOURCE_DIR + BINARY_DIR + # Prevent the following from being passed through + CONFIGURE_COMMAND + BUILD_COMMAND + INSTALL_COMMAND + TEST_COMMAND + ) + set(multiValueArgs "") + + cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Hide output if requested + if (DL_ARGS_QUIET) + set(OUTPUT_QUIET "OUTPUT_QUIET") + else() + unset(OUTPUT_QUIET) + message(STATUS "Downloading/updating ${DL_ARGS_PROJ}") + endif() + + # Set up where we will put our temporary CMakeLists.txt file and also + # the base point below which the default source and binary dirs will be + if (NOT DL_ARGS_PREFIX) + set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}") + endif() + if (NOT DL_ARGS_DOWNLOAD_DIR) + set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download") + endif() + + # Ensure the caller can know where to find the source and build directories + if (NOT DL_ARGS_SOURCE_DIR) + set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src") + endif() + if (NOT DL_ARGS_BINARY_DIR) + set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build") + endif() + set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE) + set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE) + + # Create and build a separate CMake project to carry out the download. + # If we've already previously done these steps, they will not cause + # anything to be updated, so extra rebuilds of the project won't occur. + configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in" + "${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt") + execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + ${OUTPUT_QUIET} + WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" + ) + execute_process(COMMAND ${CMAKE_COMMAND} --build . + ${OUTPUT_QUIET} + WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" + ) + +endfunction() \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/test/XMLGeneratorTest.cpp b/utilities/ArgumentParsingLib/test/XMLGeneratorTest.cpp new file mode 100644 index 0000000..87eb359 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/XMLGeneratorTest.cpp @@ -0,0 +1,225 @@ +//------------------------------------------------------------------------ +// Copyright(c) German Cancer Research Center(DKFZ), +// Software Development for Integrated Diagnostics and Therapy (SIDT). +// ALL RIGHTS RESERVED. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notices for more information. +// +// Authors: Simon Strubel, Clemens Hentschke +//------------------------------------------------------------------------ + +#include + +#include +#include "XMLGenerator.h" + + +class XMLGeneratorTest : + public testing::Test +{ +private: + std::ostringstream m_strStream; + std::streambuf* m_stdStream; +public: + std::shared_ptr generator; + + void SetUp() override{ + generator = std::make_shared("aCategory", "aName", "aDescription"); + } + + std::string readFileContents(const std::string& filename) const{ + std::ifstream fileStream(filename); + std::string content((std::istreambuf_iterator(fileStream)),(std::istreambuf_iterator())); + return content; + } + + void redirectStreamToString(std::ostream& os){ + m_stdStream = os.rdbuf(); + os.rdbuf(m_strStream.rdbuf()); + } + std::string getContentOfStream() const { + return m_strStream.str(); + } + + std::unordered_map> createBaseParametersMap(const std::string& key, char shortFlag, const std::string& description, const std::string& optionGroup, const std::string& defaultValue) const{ + std::unordered_map> baseParameters; + auto baseArguments = std::make_tuple(shortFlag, description, optionGroup, defaultValue); + baseParameters.emplace(key, baseArguments); + return baseParameters; + } + + std::unordered_map > > createAdditionalParametersMap(const std::string& key, cmdlineparsing::XMLGenerator::paramType parameterType, double minimumConstraint=0.0, double maximumConstraint=100.0, double stepConstraint=1.0, const std::vector& aVector = std::vector()) const{ + std::unordered_map > > additionalParameters; + auto additionalArguments = std::make_tuple(parameterType, minimumConstraint, maximumConstraint, stepConstraint, aVector); + additionalParameters.emplace(key, additionalArguments); + return additionalParameters; + } + +}; + +TEST_F(XMLGeneratorTest, TestXMLDouble) +{ + auto baseParameters = createBaseParametersMap("aDouble", 'd', "Test Description", "TestGroup", "42"); + auto additionalParameters = createAdditionalParametersMap("aDouble", cmdlineparsing::XMLGenerator::paramType::DOUBLE, 0.0, 100.0, 1.0); + + generator->setBaseParameters(baseParameters); + generator->setAdditionalParameters(additionalParameters); + + redirectStreamToString(std::cout); + generator->write(std::cout); + auto expectedContent = readFileContents("XMLDouble.xml"); + auto acutalContent = getContentOfStream(); + + EXPECT_EQ(expectedContent, acutalContent); +} + +TEST_F(XMLGeneratorTest, TestXMLBoolean) +{ + auto baseParameters = createBaseParametersMap("aBoolean", 'b', "Test Description", "TestGroup", "true"); + auto additionalParameters = createAdditionalParametersMap("aBoolean", cmdlineparsing::XMLGenerator::paramType::BOOLEAN); + + generator->setBaseParameters(baseParameters); + generator->setAdditionalParameters(additionalParameters); + + redirectStreamToString(std::cout); + generator->write(std::cout); + auto expectedContent = readFileContents("XMLBoolean.xml"); + auto acutalContent = getContentOfStream(); + + EXPECT_EQ(expectedContent, acutalContent); +} + +TEST_F(XMLGeneratorTest, TestXMLInput) +{ + auto baseParameters = createBaseParametersMap("anInput", 'i', "Test Description", "TestGroup", "input.txt"); + auto additionalParameters = createAdditionalParametersMap("anInput", cmdlineparsing::XMLGenerator::paramType::INPUT, 0.0, 0.0, 0.0, std::vector{"txt", "png"}); + + generator->setBaseParameters(baseParameters); + generator->setAdditionalParameters(additionalParameters); + + redirectStreamToString(std::cout); + generator->write(std::cout); + auto expectedContent = readFileContents("XMLInput.xml"); + auto acutalContent = getContentOfStream(); + + EXPECT_EQ(expectedContent, acutalContent); +} + +TEST_F(XMLGeneratorTest, TestXMLOutput) +{ + auto baseParameters = createBaseParametersMap("anOutput", 'o', "Test Description", "TestGroup", "output.xml"); + auto additionalParameters = createAdditionalParametersMap("anOutput", cmdlineparsing::XMLGenerator::paramType::OUTPUT, 0.0, 0.0, 0.0, std::vector{"xml"}); + + generator->setBaseParameters(baseParameters); + generator->setAdditionalParameters(additionalParameters); + + redirectStreamToString(std::cout); + generator->write(std::cout); + auto expectedContent = readFileContents("XMLOutput.xml"); + auto acutalContent = getContentOfStream(); + + EXPECT_EQ(expectedContent, acutalContent); +} + +TEST_F(XMLGeneratorTest, TestXMLString) +{ + auto baseParameters = createBaseParametersMap("aString", 's', "Test Description", "TestGroup", "blabla"); + auto additionalParameters = createAdditionalParametersMap("aString", cmdlineparsing::XMLGenerator::paramType::STRING); + + generator->setBaseParameters(baseParameters); + generator->setAdditionalParameters(additionalParameters); + + redirectStreamToString(std::cout); + generator->write(std::cout); + auto expectedContent = readFileContents("XMLString.xml"); + auto acutalContent = getContentOfStream(); + + EXPECT_EQ(expectedContent, acutalContent); +} + +TEST_F(XMLGeneratorTest, TestXMLInteger) +{ + auto baseParameters = createBaseParametersMap("anInt", 'i', "Test Description", "TestGroup", "5"); + auto additionalParameters = createAdditionalParametersMap("anInt", cmdlineparsing::XMLGenerator::paramType::INTEGER, 0, 10, 2); + + generator->setBaseParameters(baseParameters); + generator->setAdditionalParameters(additionalParameters); + + redirectStreamToString(std::cout); + generator->write(std::cout); + auto expectedContent = readFileContents("XMLInteger.xml"); + auto acutalContent = getContentOfStream(); + + EXPECT_EQ(expectedContent, acutalContent); +} + +TEST_F(XMLGeneratorTest, TestXMLStringEnum) +{ + auto baseParameters = createBaseParametersMap("aStringEnum", 'e', "Test Description", "TestGroup", "option1"); + auto additionalParameters = createAdditionalParametersMap("aStringEnum", cmdlineparsing::XMLGenerator::paramType::STRINGENUMERATION, 0, 0, 0, std::vector{"option1","option2","option3"}); + + generator->setBaseParameters(baseParameters); + generator->setAdditionalParameters(additionalParameters); + + redirectStreamToString(std::cout); + generator->write(std::cout); + auto expectedContent = readFileContents("XMLStringEnum.xml"); + auto acutalContent = getContentOfStream(); + + EXPECT_EQ(expectedContent, acutalContent); +} + +TEST_F(XMLGeneratorTest, TestXMLStringVector) +{ + auto baseParameters = createBaseParametersMap("aStringVector", 'v', "Test Description", "TestGroup", "foo,bar,foobar"); + auto additionalParameters = createAdditionalParametersMap("aStringVector", cmdlineparsing::XMLGenerator::paramType::STRINGVECTOR); + + generator->setBaseParameters(baseParameters); + generator->setAdditionalParameters(additionalParameters); + + redirectStreamToString(std::cout); + generator->write(std::cout); + auto expectedContent = readFileContents("XMLStringVector.xml"); + auto acutalContent = getContentOfStream(); + + EXPECT_EQ(expectedContent, acutalContent); +} + +TEST_F(XMLGeneratorTest, TestXMLComplex) +{ + auto baseParametersInput = createBaseParametersMap("anInput", 'i', "Test Description", "TestGroup", "input.txt"); + auto additionalParametersInput = createAdditionalParametersMap("anInput", cmdlineparsing::XMLGenerator::paramType::INPUT, 0, 0, 0, std::vector{"txt"}); + auto baseParametersOutput = createBaseParametersMap("anOutput", 'o', "Test Description", "TestGroup", "output.xml"); + auto additionalParametersOutput = createAdditionalParametersMap("anOutput", cmdlineparsing::XMLGenerator::paramType::OUTPUT, 0, 0, 0, std::vector{"xml"}); + auto baseParametersDouble = createBaseParametersMap("aDouble", 'd', "Test Description", "TestGroup", "42"); + auto additionalParametersDouble = createAdditionalParametersMap("aDouble", cmdlineparsing::XMLGenerator::paramType::DOUBLE, 0, 100, 1); + auto baseParametersStringVector = createBaseParametersMap("aStringVector", 'v', "Test Description", "TestGroup", "foo,bar,foobar"); + auto additionalParametersStringVector = createAdditionalParametersMap("aStringVector", cmdlineparsing::XMLGenerator::paramType::STRINGVECTOR); + auto baseParametersStringEnum = createBaseParametersMap("aStringEnum", 'e', "Test Description", "TestGroup", "option1"); + auto additionalParametersStringEnum = createAdditionalParametersMap("aStringEnum", cmdlineparsing::XMLGenerator::paramType::STRINGENUMERATION, 0, 0, 0, std::vector{"option1","option2", "option3"}); + + auto baseParametersCombined = baseParametersInput; + baseParametersCombined.insert(baseParametersOutput.begin(), baseParametersOutput.end()); + baseParametersCombined.insert(baseParametersDouble.begin(), baseParametersDouble.end()); + baseParametersCombined.insert(baseParametersStringVector.begin(), baseParametersStringVector.end()); + baseParametersCombined.insert(baseParametersStringEnum.begin(), baseParametersStringEnum.end()); + + auto additionalParametersCombined = additionalParametersInput; + additionalParametersCombined.insert(additionalParametersOutput.begin(), additionalParametersOutput.end()); + additionalParametersCombined.insert(additionalParametersDouble.begin(), additionalParametersDouble.end()); + additionalParametersCombined.insert(additionalParametersStringVector.begin(), additionalParametersStringVector.end()); + additionalParametersCombined.insert(additionalParametersStringEnum.begin(), additionalParametersStringEnum.end()); + + generator->setBaseParameters(baseParametersCombined); + generator->setAdditionalParameters(additionalParametersCombined); + + redirectStreamToString(std::cout); + generator->write(std::cout); + auto expectedContent = readFileContents("XMLComplex.xml"); + auto acutalContent = getContentOfStream(); + + EXPECT_EQ(expectedContent, acutalContent); +} + diff --git a/utilities/ArgumentParsingLib/test/data/XMLBoolean.xml b/utilities/ArgumentParsingLib/test/data/XMLBoolean.xml new file mode 100644 index 0000000..e41a79c --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/XMLBoolean.xml @@ -0,0 +1,20 @@ + + + aCategory + aName + aDescription + 1.0 + DKFZ + + + TestGroup + + aBoolean + b + aBoolean + Test Description + + true + + + diff --git a/utilities/ArgumentParsingLib/test/data/XMLComplex.xml b/utilities/ArgumentParsingLib/test/data/XMLComplex.xml new file mode 100644 index 0000000..dfd0b26 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/XMLComplex.xml @@ -0,0 +1,62 @@ + + + aCategory + aName + aDescription + 1.0 + DKFZ + + + TestGroup + + anInput + i + anInput + Test Description + + input.txt + input + + + aDouble + d + aDouble + Test Description + + 42 + + 0 + 100 + 1 + + + + aStringVector + v + aStringVector + Test Description + + foo,bar,foobar + + + anOutput + o + anOutput + Test Description + + output.xml + output + + + aStringEnum + e + aStringEnum + Test Description + + option1 + option1 + option2 + option3 + + + diff --git a/utilities/ArgumentParsingLib/test/data/XMLDouble.xml b/utilities/ArgumentParsingLib/test/data/XMLDouble.xml new file mode 100644 index 0000000..11a3582 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/XMLDouble.xml @@ -0,0 +1,25 @@ + + + aCategory + aName + aDescription + 1.0 + DKFZ + + + TestGroup + + aDouble + d + aDouble + Test Description + + 42 + + 0 + 100 + 1 + + + + diff --git a/utilities/ArgumentParsingLib/test/data/XMLInput.xml b/utilities/ArgumentParsingLib/test/data/XMLInput.xml new file mode 100644 index 0000000..5f62d6c --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/XMLInput.xml @@ -0,0 +1,21 @@ + + + aCategory + aName + aDescription + 1.0 + DKFZ + + + TestGroup + + anInput + i + anInput + Test Description + + input.txt + input + + + diff --git a/utilities/ArgumentParsingLib/test/data/XMLInteger.xml b/utilities/ArgumentParsingLib/test/data/XMLInteger.xml new file mode 100644 index 0000000..dda72f3 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/XMLInteger.xml @@ -0,0 +1,25 @@ + + + aCategory + aName + aDescription + 1.0 + DKFZ + + + TestGroup + + anInt + i + anInt + Test Description + + 5 + + 0 + 10 + 2 + + + + diff --git a/utilities/ArgumentParsingLib/test/data/XMLOutput.xml b/utilities/ArgumentParsingLib/test/data/XMLOutput.xml new file mode 100644 index 0000000..08fcd33 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/XMLOutput.xml @@ -0,0 +1,21 @@ + + + aCategory + aName + aDescription + 1.0 + DKFZ + + + TestGroup + + anOutput + o + anOutput + Test Description + + output.xml + output + + + diff --git a/utilities/ArgumentParsingLib/test/data/XMLString.xml b/utilities/ArgumentParsingLib/test/data/XMLString.xml new file mode 100644 index 0000000..5c24758 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/XMLString.xml @@ -0,0 +1,20 @@ + + + aCategory + aName + aDescription + 1.0 + DKFZ + + + TestGroup + + aString + s + aString + Test Description + + blabla + + + diff --git a/utilities/ArgumentParsingLib/test/data/XMLStringEnum.xml b/utilities/ArgumentParsingLib/test/data/XMLStringEnum.xml new file mode 100644 index 0000000..cdaf7fb --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/XMLStringEnum.xml @@ -0,0 +1,23 @@ + + + aCategory + aName + aDescription + 1.0 + DKFZ + + + TestGroup + + aStringEnum + e + aStringEnum + Test Description + + option1 + option1 + option2 + option3 + + + diff --git a/utilities/ArgumentParsingLib/test/data/XMLStringVector.xml b/utilities/ArgumentParsingLib/test/data/XMLStringVector.xml new file mode 100644 index 0000000..9f63b45 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/XMLStringVector.xml @@ -0,0 +1,20 @@ + + + aCategory + aName + aDescription + 1.0 + DKFZ + + + TestGroup + + aStringVector + v + aStringVector + Test Description + + foo,bar,foobar + + + diff --git a/utilities/ArgumentParsingLib/test/data/empty.xml b/utilities/ArgumentParsingLib/test/data/empty.xml new file mode 100644 index 0000000..d3f7cf0 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/empty.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/test/data/invalid.xml b/utilities/ArgumentParsingLib/test/data/invalid.xml new file mode 100644 index 0000000..2697ce7 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/invalid.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/test/data/notwellformed1.xml b/utilities/ArgumentParsingLib/test/data/notwellformed1.xml new file mode 100644 index 0000000..85291a0 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/notwellformed1.xml @@ -0,0 +1 @@ +not even xml \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/test/data/notwellformed2.xml b/utilities/ArgumentParsingLib/test/data/notwellformed2.xml new file mode 100644 index 0000000..9383344 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/notwellformed2.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/test/data/notwellformed3.xml b/utilities/ArgumentParsingLib/test/data/notwellformed3.xml new file mode 100644 index 0000000..acfed54 --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/notwellformed3.xml @@ -0,0 +1,2 @@ + + + + + + multitoken-numbers + 99 + 999 + + + composing-numbers + 10 + 20 + 30 + + + flag + + + + output-file + out.txt + + \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/test/data/simple.xml b/utilities/ArgumentParsingLib/test/data/simple.xml new file mode 100644 index 0000000..4d8f21f --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/simple.xml @@ -0,0 +1,6 @@ + + + + help + + \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/test/data/working.xml b/utilities/ArgumentParsingLib/test/data/working.xml new file mode 100644 index 0000000..e77f7eb --- /dev/null +++ b/utilities/ArgumentParsingLib/test/data/working.xml @@ -0,0 +1,17 @@ + + + + + multitoken-numbers + 99 + 999 + + + flag + + + + output-file + out.txt + + \ No newline at end of file diff --git a/utilities/ArgumentParsingLib/test/parseXMLTest.cpp b/utilities/ArgumentParsingLib/test/parseXMLTest.cpp new file mode 100644 index 0000000..4eb05dc --- /dev/null +++ b/utilities/ArgumentParsingLib/test/parseXMLTest.cpp @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------ +// Copyright(c) German Cancer Research Center(DKFZ), +// Software Development for Integrated Diagnostics and Therapy (SIDT). +// ALL RIGHTS RESERVED. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notices for more information. +// +// Author: Sascha Diatschuk +//------------------------------------------------------------------------ + +#include +#include +#include "parseXML.h" + +class ParseXMLTest : public testing::Test +{ +protected: + boost::program_options::options_description ruleSet; +}; + +TEST_F(ParseXMLTest, InvalidFileName) +{ + EXPECT_THROW(cmdlineparsing::parseXML(std::string(), ruleSet), + boost::program_options::reading_file); +} + +TEST_F(ParseXMLTest, NoOptionsWithLongName) +{ + boost::shared_ptr rule(new + boost::program_options::option_description); + ruleSet.add(rule); + EXPECT_THROW(cmdlineparsing::parseXML(std::string(), ruleSet), + boost::program_options::error); +} + +TEST_F(ParseXMLTest, NotWellFormedXML) +{ + EXPECT_THROW(cmdlineparsing::parseXML("notwellformed1.xml", ruleSet), + boost::property_tree::xml_parser_error); + EXPECT_THROW(cmdlineparsing::parseXML("notwellformed2.xml", ruleSet), + boost::property_tree::xml_parser_error); + EXPECT_THROW(cmdlineparsing::parseXML("notwellformed3.xml", ruleSet), + boost::property_tree::xml_parser_error); +} + +TEST_F(ParseXMLTest, InvalidXML) +{ + EXPECT_THROW(cmdlineparsing::parseXML("invalid.xml", ruleSet), + boost::property_tree::ptree_bad_path); +} + +TEST_F(ParseXMLTest, EmptyParameterList) +{ + EXPECT_TRUE(cmdlineparsing::parseXML("empty.xml", ruleSet).options.empty()); +} + +TEST_F(ParseXMLTest, NotInRuleSet) +{ + EXPECT_TRUE(cmdlineparsing::parseXML("working.xml", ruleSet).options.empty()); +} + +TEST_F(ParseXMLTest, WorkingFile) +{ + ruleSet.add_options() + ("multitoken-numbers", boost::program_options::value >()->multitoken()) + ("flag", "") + ("output-file", boost::program_options::value()); + + boost::program_options::parsed_options results = + cmdlineparsing::parseXML("working.xml", ruleSet); + + // Parsed options don't have their specific type yet. + EXPECT_EQ("multitoken-numbers", results.options[0].string_key); + EXPECT_EQ(2, results.options[0].value.size()); + EXPECT_EQ("99", results.options[0].value[0]); + EXPECT_EQ("999", results.options[0].value[1]); + + EXPECT_EQ("flag", results.options[1].string_key); + EXPECT_TRUE(results.options[1].value.empty()); + + EXPECT_EQ("output-file", results.options[2].string_key); + EXPECT_EQ(1, results.options[2].value.size()); + EXPECT_EQ("out.txt", results.options[2].value[0]); +} \ No newline at end of file