Add portable option parser.
GitOrigin-RevId: 0d4f7e2f5bec4826e1c12e3aa1aee642fcf6da07
This commit is contained in:
parent
4bb6fe7e78
commit
c46910d75f
@ -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;
|
||||
|
@ -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()
|
||||
|
153
tdutils/td/utils/OptionParser.cpp
Normal file
153
tdutils/td/utils/OptionParser.cpp
Normal file
@ -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 <cstring>
|
||||
#include <unordered_map>
|
||||
|
||||
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<Status(Slice)> 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<Status(Slice)> 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<Status(void)> callback) {
|
||||
// Ouch. There must be some better way
|
||||
add_option(Option::Type::NoArg, short_key, long_key, description,
|
||||
std::bind([](std::function<Status(void)> &func, Slice) { return func(); }, std::move(callback),
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
Result<vector<char *>> OptionParser::run(int argc, char *argv[]) {
|
||||
std::unordered_map<char, const Option *> short_options;
|
||||
std::unordered_map<string, const Option *> 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<char *> 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 << "<arg>";
|
||||
}
|
||||
sb << "\t" << opt.description;
|
||||
sb << "\n";
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
} // namespace td
|
@ -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<Status(void)> callback);
|
||||
|
||||
Result<int> run(int argc, char *argv[]) TD_WARN_UNUSED_RESULT;
|
||||
// returns found non-option parameters
|
||||
Result<vector<char *>> 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<Option> options_;
|
@ -1,123 +0,0 @@
|
||||
//
|
||||
// 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/OptionsParser.h"
|
||||
|
||||
#if TD_HAVE_GETOPT
|
||||
#include "getopt.h"
|
||||
#endif
|
||||
|
||||
#if !TD_WINDOWS
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace td {
|
||||
|
||||
void OptionsParser::set_description(string description) {
|
||||
description_ = std::move(description);
|
||||
}
|
||||
|
||||
void OptionsParser::add_option(Option::Type type, char short_key, Slice long_key, Slice description,
|
||||
std::function<Status(Slice)> callback) {
|
||||
options_.push_back(Option{type, short_key, long_key.str(), description.str(), std::move(callback)});
|
||||
}
|
||||
|
||||
void OptionsParser::add_option(char short_key, Slice long_key, Slice description,
|
||||
std::function<Status(Slice)> callback) {
|
||||
add_option(Option::Type::Arg, short_key, long_key, description, std::move(callback));
|
||||
}
|
||||
|
||||
void OptionsParser::add_option(char short_key, Slice long_key, Slice description,
|
||||
std::function<Status(void)> callback) {
|
||||
// Ouch. There must be some better way
|
||||
add_option(Option::Type::NoArg, short_key, long_key, description,
|
||||
std::bind([](std::function<Status(void)> &func, Slice) { return func(); }, std::move(callback),
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
Result<int> OptionsParser::run(int argc, char *argv[]) {
|
||||
#if TD_HAVE_GETOPT
|
||||
char buff[1024];
|
||||
StringBuilder sb(MutableSlice{buff, sizeof(buff)});
|
||||
for (auto &opt : options_) {
|
||||
sb << opt.short_key;
|
||||
if (opt.type == Option::Type::Arg) {
|
||||
sb << ":";
|
||||
}
|
||||
}
|
||||
if (sb.is_error()) {
|
||||
return Status::Error("Can't parse options");
|
||||
}
|
||||
CSlice short_options = sb.as_cslice();
|
||||
|
||||
vector<option> long_options;
|
||||
for (auto &opt : options_) {
|
||||
if (opt.long_key.empty()) {
|
||||
continue;
|
||||
}
|
||||
option o;
|
||||
o.flag = nullptr;
|
||||
o.val = opt.short_key;
|
||||
o.has_arg = opt.type == Option::Type::Arg ? required_argument : no_argument;
|
||||
o.name = opt.long_key.c_str();
|
||||
long_options.push_back(o);
|
||||
}
|
||||
long_options.push_back({nullptr, 0, nullptr, 0});
|
||||
|
||||
while (true) {
|
||||
int opt_i = getopt_long(argc, argv, short_options.c_str(), &long_options[0], nullptr);
|
||||
if (opt_i == ':') {
|
||||
return Status::Error("Missing argument");
|
||||
}
|
||||
if (opt_i == '?') {
|
||||
return Status::Error("Unrecognized option");
|
||||
}
|
||||
if (opt_i == -1) {
|
||||
break;
|
||||
}
|
||||
bool found = false;
|
||||
for (auto &opt : options_) {
|
||||
if (opt.short_key == opt_i) {
|
||||
Slice arg;
|
||||
if (opt.type == Option::Type::Arg) {
|
||||
arg = Slice(optarg);
|
||||
}
|
||||
auto status = opt.arg_callback(arg);
|
||||
if (status.is_error()) {
|
||||
return std::move(status);
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return Status::Error("Unknown argument");
|
||||
}
|
||||
}
|
||||
return optind;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
StringBuilder &operator<<(StringBuilder &sb, const OptionsParser &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 != OptionsParser::Option::Type::NoArg) {
|
||||
sb << "<arg>";
|
||||
}
|
||||
sb << "\t" << opt.description;
|
||||
sb << "\n";
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
} // namespace td
|
@ -5,5 +5,4 @@
|
||||
#cmakedefine01 TD_HAVE_CRC32C
|
||||
#cmakedefine01 TD_HAVE_COROUTINES
|
||||
#cmakedefine01 TD_HAVE_ABSL
|
||||
#cmakedefine01 TD_HAVE_GETOPT
|
||||
#cmakedefine01 TD_FD_DEBUG
|
||||
|
91
tdutils/test/OptionParser.cpp
Normal file
91
tdutils/test/OptionParser.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
//
|
||||
// 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/common.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/OptionParser.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Status.h"
|
||||
#include "td/utils/tests.h"
|
||||
|
||||
TEST(OptionParser, run) {
|
||||
td::OptionParser options;
|
||||
options.set_description("test description");
|
||||
|
||||
td::vector<td::string> args;
|
||||
auto run_option_parser = [&](td::string command_line) {
|
||||
args = td::full_split(command_line, ' ');
|
||||
td::vector<char *> argv;
|
||||
argv.push_back("exename");
|
||||
for (auto &arg : args) {
|
||||
argv.push_back(&arg[0]);
|
||||
}
|
||||
return options.run(static_cast<int>(argv.size()), &argv[0]);
|
||||
};
|
||||
|
||||
td::uint64 chosen_options = 0;
|
||||
td::vector<td::string> chosen_parameters;
|
||||
auto test_success = [&](td::string command_line, td::uint64 expected_options,
|
||||
td::vector<td::string> expected_parameters, td::vector<td::string> expected_result) {
|
||||
chosen_options = 0;
|
||||
chosen_parameters.clear();
|
||||
auto result = run_option_parser(command_line);
|
||||
ASSERT_TRUE(result.is_ok());
|
||||
ASSERT_EQ(expected_options, chosen_options);
|
||||
ASSERT_EQ(expected_parameters, chosen_parameters);
|
||||
ASSERT_EQ(expected_result.size(), result.ok().size());
|
||||
for (size_t i = 0; i < expected_result.size(); i++) {
|
||||
ASSERT_STREQ(expected_result[i], td::string(result.ok()[i]));
|
||||
}
|
||||
};
|
||||
auto test_fail = [&](td::string command_line) {
|
||||
auto result = run_option_parser(command_line);
|
||||
ASSERT_TRUE(result.is_error());
|
||||
};
|
||||
|
||||
options.add_option('q', "", "", [&] {
|
||||
chosen_options += 1;
|
||||
return td::Status::OK();
|
||||
});
|
||||
options.add_option('\0', "http-port2", "", [&] {
|
||||
chosen_options += 10;
|
||||
return td::Status::OK();
|
||||
});
|
||||
options.add_option('p', "http-port", "", [&](td::Slice parameter) {
|
||||
chosen_options += 100;
|
||||
chosen_parameters.push_back(parameter.str());
|
||||
return td::Status::OK();
|
||||
});
|
||||
options.add_option('v', "test", "", [&] {
|
||||
chosen_options += 1000;
|
||||
return td::Status::OK();
|
||||
});
|
||||
|
||||
test_fail("-http-port2");
|
||||
test_success("-", 0, {}, {"-"});
|
||||
test_fail("--http-port");
|
||||
test_fail("--http-port3");
|
||||
test_fail("--http-por");
|
||||
test_fail("--http-port2=1");
|
||||
test_fail("--q");
|
||||
test_fail("-qvp");
|
||||
test_fail("-p");
|
||||
test_fail("-u");
|
||||
test_success("-q", 1, {}, {});
|
||||
test_success("-vvvvvvvvvv", 10000, {}, {});
|
||||
test_success("-qpv", 101, {"v"}, {});
|
||||
test_success("-qp -v", 101, {"-v"}, {});
|
||||
test_success("-qp --http-port2", 101, {"--http-port2"}, {});
|
||||
test_success("-qp -- -v", 1101, {"--"}, {});
|
||||
test_success("-qvqvpqv", 2102, {"qv"}, {});
|
||||
test_success("aba --http-port2 caba --http-port2 dabacaba", 20, {}, {"aba", "caba", "dabacaba"});
|
||||
test_success("das -pqwerty -- -v asd --http-port", 100, {"qwerty"}, {"das", "-v", "asd", "--http-port"});
|
||||
test_success("-p option --http-port option2 --http-port=option3 --http-port=", 400,
|
||||
{"option", "option2", "option3", ""}, {});
|
||||
test_success("", 0, {}, {});
|
||||
test_success("a", 0, {}, {"a"});
|
||||
test_success("", 0, {}, {});
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
int main(int argc, char **argv) {
|
||||
td::init_openssl_threads();
|
||||
|
||||
// TODO port OptionsParser to Windows
|
||||
// TODO use OptionParser
|
||||
td::TestsRunner &runner = td::TestsRunner::get_default();
|
||||
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
|
||||
for (int i = 1; i < argc; i++) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user