/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #ifndef _IFC_SIMPLE_PROGRAM_OPTIONS_H #define _IFC_SIMPLE_PROGRAM_OPTIONS_H #include "OdString.h" #define STL_USING_VECTOR #define STL_USING_SET #define STL_USING_STREAM #define STL_USING_STRING #define STL_USING_MEMORY #include "OdaSTL.h" #include #include "TD_PackPush.h" namespace OdDAI { namespace utils { template bool tryParseTemplate(const OdString& from, const char* bufferTemplate, TType& to) { return sscanf(((OdAnsiString)from).c_str(), bufferTemplate, &to) == 1; } static bool tryParse(const OdString& from, unsigned int& to) { int valueProxy = 0; if (tryParseTemplate(from, "%d", valueProxy)) { return false; } to = static_cast(valueProxy); return true; } static bool tryParse(const OdString& from, int& to) { return tryParseTemplate(from, "%d", to); } static bool tryParse(const OdString& from, float& to) { return tryParseTemplate(from, "%f", to); } static bool tryParse(const OdString& from, double& to) { return tryParseTemplate(from, "%lf", to); } static bool tryParse(const OdString& from, OdString& to) { to = from; return true; } static bool tryParse(const OdString& from, OdAnsiString& to) { to = static_cast(from); return true; } static bool tryParse(const OdString& from, std::string& to) { to = static_cast(from).c_str(); return true; } template inline T defaultValueForStream(T& value) { return value; } inline std::string defaultValueForStream(bool& value) { return (value) ? "true" : "false"; } inline std::string defaultValueForStream(OdString& value) { return value.isEmpty() ? std::string("") : std::string(OdAnsiString(value).c_str()); } template inline std::string defaultValueForStream(std::vector& valueCollection) { if (valueCollection.empty()) { return std::string("collection[ empty ]"); } std::stringstream valuesToReproduce; valuesToReproduce << "collection"; std::string putBefore = "[ "; for (auto& value : valueCollection) { valuesToReproduce << putBefore << defaultValueForStream(value); putBefore = ", "; } valuesToReproduce << std::string(" ]"); return valuesToReproduce.str(); } // try to implement simple program_options analog class option_item { public: virtual ~option_item() {} virtual bool read(OdString next) = 0; virtual void showError(std::ostream& stream) = 0; virtual void info(std::ostream& stream, int place) = 0; virtual void useSample(std::ostream& stream) = 0; virtual bool isOptional() = 0; virtual bool isHardPlaced() = 0; }; template class base_option : public option_item { public: base_option(TValue& value, const OdString& key, const OdString& name, const OdString& description, bool isOptional = true, bool isHardPlaced = false) : m_value(value) , m_key(OdAnsiString(key).c_str()) , m_name(OdAnsiString(name).c_str()) , m_description(OdAnsiString(description).c_str()) , m_isOptional(isOptional) , m_isHardPlaced(isHardPlaced) { } void showError(std::ostream& stream) override { stream << "Error parameter value: " << m_name << std::endl; } void info(std::ostream& stream, int place) override { stream << m_name; stream << " - "; stream << m_description; if (isOptional()) { stream << " Is optional parameter. Default value is \"" << defaultValueForStream(m_value) << "\"."; } if (isHardPlaced()) { stream << " Should be on the " << place + 1 << " place."; } stream << std::endl; } void setDescription(const std::string& description) { m_description = description; } bool isOptional() override { return m_isOptional; } bool isHardPlaced() override { return m_isHardPlaced; } protected: TValue& m_value; std::string m_key; std::string m_name; std::string m_description; bool m_isOptional = true; bool m_isHardPlaced = false; }; /// Hard placed option. Should go on the defined place. /// /// Example how to use: /// OdDAI::utils::argv_parser commandLineParser("some.exe"); /// OdString szTargetFileName; /// commandLineParser.add_param(std::make_shared>(szTargetFileName, "File name", "target filename")); /// /// Example in args: /// some.exe "f:/path_to_some_file.txt" - [szTargetFileName == "f:/path_to_some_file.txt"] template class hardplace_value_option : public base_option { public: hardplace_value_option(TValue& value, const OdString& name, const OdString& description, bool isOptional = false) : base_option(value, "", name, description, isOptional, true) { } bool read(OdString nextParam) override { return tryParse(nextParam, this->m_value); } void useSample(std::ostream& stream) override { if (this->isOptional()) { stream << "["; } stream << "<" << this->m_name << ">"; if (this->isOptional()) { stream << "]"; } } }; /// Flag options value. Set flag to true if option name is at arguments list /// /// Example how to use: /// OdDAI::utils::argv_parser commandLineParser("some.exe"); /// bool disableText = false; /// commandLineParser.add_param(std::make_shared(disableText, "-textDisable", "disable text output.")); /// /// Example in args: /// some.exe ... -disableText - [disableText == true] /// some.exe ... -disableText=true - [disableText == true] /// some.exe ... -disableText=false - [disableText == false] /// some.exe ... - [disableText == false] class flag_value_option : public base_option { public: flag_value_option(bool& value, const OdString& key, const OdString& description, bool isHardPlaced = false) : base_option(value, key, key, description, true, isHardPlaced) { } bool read(OdString nextParam) override { if (nextParam == OdString(this->m_key.c_str())) { m_value = true; return true; } OdString trigger = OdString(this->m_key.c_str()) + "="; if (nextParam.find(trigger) != 0) { return false; } auto testToParse = nextParam.right(nextParam.getLength() - trigger.getLength()); if (testToParse == "true") { m_value = true; } return true; } void useSample(std::ostream& stream) override { if (this->isOptional()) { stream << "["; } stream << this->m_key; if (this->isOptional()) { stream << "]"; } } }; class promote_option : public option_item { public: promote_option(const OdString& key, const OdString& description) : m_key(OdAnsiString(key).c_str()) , m_description(OdAnsiString(description).c_str()) { } void info(std::ostream& stream, int place) override { stream << this->m_key; stream << " - "; stream << this->m_description; stream << std::endl; } virtual void useSample(std::ostream& stream) { stream << "["; stream << "<" << this->m_key << ">"; stream << "]"; } bool read(OdString next) override { return false; } void showError(std::ostream& stream) override {} bool isOptional() override { return true; } bool isHardPlaced() override { return false; } private: std::string m_key; std::string m_description; }; /// Single key->value option. Finds and parse option like key=value in argument list. /// /// Example how to use: /// OdDAI::utils::argv_parser commandLineParser("some.exe"); /// OdString benchmarkFileName = "c:/default_file_name.bnc"; /// commandLineParser.add_param(std::make_shared>(benchmarkFileName, "-benchmark", "path to file benchmark, to compare. Does not used if empty.")); /// /// Example in args: /// some.exe ... -benchmark="c:/some_place/file_name.bnc" - [benchmarkFileName="c:/some_place/file_name.bnc"] /// some.exe ... - [benchmarkFileName="c:/default_file_name.bnc"] template class key_value_option : public base_option { public: key_value_option(TValue& value, const OdString& key, const OdString& description, bool isOptional = true, bool isHardPlaced = false) : base_option(value, key, key, description, isOptional, isHardPlaced) { } bool read(OdString nextParam) override { OdString trigger = OdString(this->m_key.c_str()) + "="; if (nextParam.find(trigger) != 0) { return false; } auto testToParse = nextParam.right(nextParam.getLength() - trigger.getLength()); if (!tryParse(testToParse, this->m_value)) { ODA_ASSERT(0 && "param parser error"); } return true; } void useSample(std::ostream& stream) override { if (this->isOptional()) { stream << "["; } stream << this->m_key << "="; if (this->isOptional()) { stream << "]"; } } }; /// Multiple key->value option. Finds and parse multiple occurrences of options like key=value in argument list. /// /// Example how to use: /// OdDAI::utils::argv_parser commandLineParser("some.exe"); /// std::vector ifcFiles = {"c:/a1.txt","c:/a2.txt"}; /// commandLineParser.add_param(std::make_shared>(ifcFiles, "-ifcFile", "the list of compared files")); /// /// Example in args: /// some.exe ... -ifcFile="c:/1.txt" ... -ifcFile="c:/2.txt" - [ifcFiles == ["c:/1.txt", "c:/2.txt"]] /// some.exe ... - [ifcFiles == ["c:/a1.txt","c:/a2.txt"]] template class collection_key_value_option : public base_option > { public: collection_key_value_option(std::vector& value, const OdString& key, const OdString& description, bool isOptional = true, bool isHardPlaced = false) : base_option>(value, key, key, description, isOptional, isHardPlaced) { } bool read(OdString nextParam) override { OdString trigger = OdString(this->m_key.c_str()) + "="; if (nextParam.find(trigger) != 0) { return false; } auto testToParse = nextParam.right(nextParam.getLength() - trigger.getLength()); TValue transitValue{}; if (!tryParse(testToParse, transitValue)) { ODA_ASSERT(0 && "param parser error"); } if (!m_wasDefaultValueReset) { this->m_value.clear(); m_wasDefaultValueReset = true; } this->m_value.push_back(transitValue); return true; } void useSample(std::ostream& stream) override { if (this->isOptional()) { stream << "["; } stream << this->m_key << "= "; stream << this->m_key << "= ..."; stream << this->m_key << "="; if (this->isOptional()) { stream << "]"; } } private: bool m_wasDefaultValueReset = false; }; enum class ParseResult { failed, succeed, showHelp, }; class argv_parser { public: argv_parser(const OdString& applicationName, bool hasHelpOption = true) : m_applicationName(OdAnsiString(applicationName).c_str()) { if (hasHelpOption) { m_helpOption = std::make_shared(m_showHelp, "-help", "show descriptions for all available parameters."); } } bool setCustomHelpDescription(const std::string& newDescription) { if (m_helpOption == nullptr) { return false; } m_helpOption->setDescription(newDescription); return true; } bool add_param(const std::shared_ptr& option) { if (option == nullptr) { return false; } m_optionsCollection.push_back(option); return true; } ParseResult parse(const std::vector& argumentList, std::ostream& parseOutput) { switch (argumentList.size()) { case 0: { for (auto& option : m_optionsCollection) { if (!option->isOptional()) { showHelp(parseOutput); return ParseResult::showHelp; } } } break; case 1: { if (m_helpOption && m_helpOption->read(argumentList[0]) && m_showHelp) { showHelp(parseOutput); return ParseResult::showHelp; } } break; } std::vector notHardOptionsCollection; std::vector notHardArgumentList; // check hard placed unsigned int optionIndex = 0; for (auto& argument : argumentList) { if (m_optionsCollection.size() == optionIndex) { notHardArgumentList.push_back(argument); continue; } auto& option = m_optionsCollection[optionIndex]; if (option->isHardPlaced()) { if (!option->read(argument)) { option->showError(parseOutput); return ParseResult::failed; } } else { notHardOptionsCollection.push_back(option); notHardArgumentList.push_back(argument); } ++optionIndex; } while (m_optionsCollection.size() > optionIndex) { if (m_optionsCollection[optionIndex]->isHardPlaced() && !m_optionsCollection[optionIndex]->isOptional()) { m_optionsCollection[optionIndex]->showError(parseOutput); return ParseResult::failed; } notHardOptionsCollection.push_back(m_optionsCollection[optionIndex]); ++optionIndex; } std::set triggeredOptionsCollection; for (auto argumentIterator = notHardArgumentList.begin(); argumentIterator != notHardArgumentList.end(); ) { bool wasRead = false; for (auto optionIterator = notHardOptionsCollection.begin(); optionIterator != notHardOptionsCollection.end(); ++optionIterator) { if ((*optionIterator)->read(*argumentIterator)) { triggeredOptionsCollection.insert(*optionIterator); wasRead = true; break; } } if (wasRead) { argumentIterator = notHardArgumentList.erase(argumentIterator); } else { ++argumentIterator; } } for (auto& option : notHardOptionsCollection) { if (triggeredOptionsCollection.find(option) != triggeredOptionsCollection.end()) { continue; } if (!option->isOptional()) { option->showError(parseOutput); return ParseResult::failed; } } return ParseResult::succeed; } void showHelp(std::ostream& helpOutput) const { helpOutput << "Use case:" << std::endl; helpOutput << m_applicationName << " "; for (auto& option : m_optionsCollection) { option->useSample(helpOutput); helpOutput << " "; } if (m_helpOption) { m_helpOption->useSample(helpOutput); } helpOutput << "\n\nWhere:" << std::endl; int optionIndex = 0; for (auto& option : m_optionsCollection) { option->info(helpOutput, optionIndex); ++optionIndex; } if (m_helpOption) { m_helpOption->info(helpOutput, optionIndex); } } private: using option_item_ptr = std::shared_ptr; std::vector m_optionsCollection; std::shared_ptr m_helpOption; bool m_showHelp = false; std::string m_applicationName; }; } } /* Working code example: OdString szTargetFileName; OdString benchmarkFileName; OdString createBenchmarkFileName; bool disableText = false; bool disableHtml = false; bool disableConsole = false; bool disableProgress = false; std::vector testParams = {"1", "2", "3"}; OdDAI::utils::argv_parser commandLineParser("ExIfcModelValidator"); commandLineParser.add_param(std::make_shared>(szTargetFileName, "File name", "target filename")); commandLineParser.add_param(std::make_shared(disableText, "-textDisable", "disable text output.")); commandLineParser.add_param(std::make_shared(disableHtml, "-htmlDisable", "disable html output.")); commandLineParser.add_param(std::make_shared(disableConsole, "-consoleDisable", "disable validation console output.")); commandLineParser.add_param(std::make_shared(disableProgress, "-progressDisable", "disable progress output.")); commandLineParser.add_param(std::make_shared>(benchmarkFileName, "-benchmark", "path to file benchmark, to compare. Does not used if empty.")); commandLineParser.add_param(std::make_shared>(createBenchmarkFileName, "-createBenchmark", "creates benchmark and save it to the target path. Does not used if empty. ")); commandLineParser.add_param(std::make_shared>(testParams, "-ifcFile", "the list of compared files")); std::vector argumentList(argv + 1, argv + argc); std::stringstream executionStream; if (!commandLineParser.parse(argumentList, executionStream)) { odPrintConsoleString(L"\nExIfcModelValidator sample program. Copyright (c) 2025, Open Design Alliance\n"); odPrintConsoleString(OdString(executionStream.str().c_str())); odPrintConsoleString(L"\nPress ENTER to continue...\n"); return 1; } */ #include "TD_PackPop.h" #endif // _IFC_SIMPLE_PROGRAM_OPTIONS_H