From c46910d75f6b6c1341330f2b80da9958b50ca2da Mon Sep 17 00:00:00 2001 From: levlam Date: Wed, 17 Jun 2020 03:04:36 +0300 Subject: [PATCH] Add portable option parser. GitOrigin-RevId: 0d4f7e2f5bec4826e1c12e3aa1aee642fcf6da07 --- td/telegram/cli.cpp | 2 +- tdutils/CMakeLists.txt | 17 +- tdutils/td/utils/OptionParser.cpp | 153 ++++++++++++++++++ .../utils/{OptionsParser.h => OptionParser.h} | 7 +- tdutils/td/utils/OptionsParser.cpp | 123 -------------- tdutils/td/utils/config.h.in | 1 - tdutils/test/OptionParser.cpp | 91 +++++++++++ test/main.cpp | 2 +- 8 files changed, 253 insertions(+), 143 deletions(-) create mode 100644 tdutils/td/utils/OptionParser.cpp rename tdutils/td/utils/{OptionsParser.h => OptionParser.h} (88%) delete mode 100644 tdutils/td/utils/OptionsParser.cpp create mode 100644 tdutils/test/OptionParser.cpp diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 5e41c8558..e438ebfc8 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -4331,7 +4331,7 @@ void main(int argc, char **argv) { } return std::string(); }(std::getenv("TD_API_HASH")); - // TODO port OptionsParser to Windows + // TODO use OptionParser for (int i = 1; i < argc; i++) { if (!std::strcmp(argv[i], "--test")) { use_test_dc = true; diff --git a/tdutils/CMakeLists.txt b/tdutils/CMakeLists.txt index 22718beb8..e3bfa09e2 100644 --- a/tdutils/CMakeLists.txt +++ b/tdutils/CMakeLists.txt @@ -4,14 +4,6 @@ if (NOT DEFINED CMAKE_INSTALL_LIBDIR) set(CMAKE_INSTALL_LIBDIR "lib") endif() -if (WIN32) - if (WINGETOPT_FOUND) - set(TD_HAVE_GETOPT 1) - endif() -else() - set(TD_HAVE_GETOPT 1) -endif() - if (NOT ZLIB_FOUND) find_package(ZLIB) endif() @@ -104,7 +96,7 @@ set(TDUTILS_SOURCE td/utils/misc.cpp td/utils/MimeType.cpp td/utils/MpmcQueue.cpp - td/utils/OptionsParser.cpp + td/utils/OptionParser.cpp td/utils/PathView.cpp td/utils/Random.cpp td/utils/SharedSlice.cpp @@ -222,7 +214,7 @@ set(TDUTILS_SOURCE td/utils/ObjectPool.h td/utils/Observer.h td/utils/optional.h - td/utils/OptionsParser.h + td/utils/OptionParser.h td/utils/OrderedEventsProcessor.h td/utils/overloaded.h td/utils/Parser.h @@ -280,6 +272,7 @@ set(TDUTILS_TEST_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/test/MpmcQueue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/MpmcWaiter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/MpscLinkQueue.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/OptionParser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/OrderedEventsProcessor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/port.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/pq.cpp @@ -324,10 +317,6 @@ if (ABSL_FOUND) target_link_libraries(tdutils PUBLIC absl::flat_hash_map absl::flat_hash_set absl::hash) endif() -if (WIN32 AND WINGETOPT_FOUND) - target_link_libraries(tdutils PRIVATE wingetopt) -endif() - if (ANDROID) target_link_libraries(tdutils PRIVATE log) endif() diff --git a/tdutils/td/utils/OptionParser.cpp b/tdutils/td/utils/OptionParser.cpp new file mode 100644 index 000000000..2b6d43951 --- /dev/null +++ b/tdutils/td/utils/OptionParser.cpp @@ -0,0 +1,153 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/OptionParser.h" + +#include "td/utils/misc.h" + +#include +#include + +namespace td { + +void OptionParser::set_description(string description) { + description_ = std::move(description); +} + +void OptionParser::add_option(Option::Type type, char short_key, Slice long_key, Slice description, + std::function callback) { + options_.push_back(Option{type, short_key, long_key.str(), description.str(), std::move(callback)}); +} + +void OptionParser::add_option(char short_key, Slice long_key, Slice description, + std::function callback) { + add_option(Option::Type::Arg, short_key, long_key, description, std::move(callback)); +} + +void OptionParser::add_option(char short_key, Slice long_key, Slice description, std::function callback) { + // Ouch. There must be some better way + add_option(Option::Type::NoArg, short_key, long_key, description, + std::bind([](std::function &func, Slice) { return func(); }, std::move(callback), + std::placeholders::_1)); +} + +Result> OptionParser::run(int argc, char *argv[]) { + std::unordered_map short_options; + std::unordered_map long_options; + for (auto &opt : options_) { + if (opt.short_key != '\0') { + short_options[opt.short_key] = &opt; + } + if (!opt.long_key.empty()) { + long_options[opt.long_key] = &opt; + } + } + + vector non_options; + for (int arg_pos = 1; arg_pos < argc; arg_pos++) { + const char *arg = argv[arg_pos]; + if (arg[0] != '-' || arg[1] == '\0') { + non_options.push_back(argv[arg_pos]); + continue; + } + if (arg[1] == '-' && arg[2] == '\0') { + // "--"; after it everything is non-option + while (++arg_pos < argc) { + non_options.push_back(argv[arg_pos]); + } + break; + } + + if (arg[1] == '-') { + // long option + Slice long_arg(arg + 2, std::strlen(arg + 2)); + Slice param; + auto equal_pos = long_arg.find('='); + bool has_equal = equal_pos != Slice::npos; + if (has_equal) { + param = long_arg.substr(equal_pos + 1); + long_arg = long_arg.substr(0, equal_pos); + } + + auto it = long_options.find(long_arg.str()); + if (it == long_options.end()) { + return Status::Error(PSLICE() << "Option " << long_arg << " was unrecognized"); + } + + auto option = it->second; + switch (option->type) { + case Option::Type::NoArg: + if (has_equal) { + return Status::Error(PSLICE() << "Option " << long_arg << " must not have argument"); + } + break; + case Option::Type::Arg: + if (!has_equal) { + if (++arg_pos == argc) { + return Status::Error(PSLICE() << "Option " << long_arg << " must have argument"); + } + param = Slice(argv[arg_pos], std::strlen(argv[arg_pos])); + } + break; + default: + UNREACHABLE(); + } + + TRY_STATUS(option->arg_callback(param)); + continue; + } + + for (size_t opt_pos = 1; arg[opt_pos] != '\0'; opt_pos++) { + auto it = short_options.find(arg[opt_pos]); + if (it == short_options.end()) { + return Status::Error(PSLICE() << "Option " << arg[opt_pos] << " was unrecognized"); + } + + auto option = it->second; + Slice param; + switch (option->type) { + case Option::Type::NoArg: + // nothing to do + break; + case Option::Type::Arg: + if (arg[opt_pos + 1] == '\0') { + if (++arg_pos == argc) { + return Status::Error(PSLICE() << "Option " << arg[opt_pos] << " must have argument"); + } + param = Slice(argv[arg_pos], std::strlen(argv[arg_pos])); + } else { + param = Slice(arg + opt_pos + 1, std::strlen(arg + opt_pos + 1)); + opt_pos += param.size(); + } + break; + default: + UNREACHABLE(); + } + + TRY_STATUS(option->arg_callback(param)); + } + } + + return std::move(non_options); +} + +StringBuilder &operator<<(StringBuilder &sb, const OptionParser &o) { + sb << o.description_ << "\n"; + for (auto &opt : o.options_) { + sb << "-" << opt.short_key; + if (!opt.long_key.empty()) { + sb << "|--" << opt.long_key; + } + if (opt.type != OptionParser::Option::Type::NoArg) { + sb << ""; + } + sb << "\t" << opt.description; + sb << "\n"; + } + return sb; +} + +} // namespace td diff --git a/tdutils/td/utils/OptionsParser.h b/tdutils/td/utils/OptionParser.h similarity index 88% rename from tdutils/td/utils/OptionsParser.h rename to tdutils/td/utils/OptionParser.h index fea61a1ca..ecc520090 100644 --- a/tdutils/td/utils/OptionsParser.h +++ b/tdutils/td/utils/OptionParser.h @@ -15,7 +15,7 @@ namespace td { -class OptionsParser { +class OptionParser { class Option { public: enum class Type { NoArg, Arg }; @@ -36,9 +36,10 @@ class OptionsParser { void add_option(char short_key, Slice long_key, Slice description, std::function callback); - Result run(int argc, char *argv[]) TD_WARN_UNUSED_RESULT; + // returns found non-option parameters + Result> run(int argc, char *argv[]) TD_WARN_UNUSED_RESULT; - friend StringBuilder &operator<<(StringBuilder &sb, const OptionsParser &o); + friend StringBuilder &operator<<(StringBuilder &sb, const OptionParser &o); private: vector