2020-06-17 02:04:36 +02:00
|
|
|
//
|
2022-12-31 22:28:08 +01:00
|
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
2020-06-17 02:04:36 +02:00
|
|
|
//
|
|
|
|
// 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"
|
|
|
|
|
2022-02-09 15:05:27 +01:00
|
|
|
#include "td/utils/FlatHashMap.h"
|
2020-06-26 01:24:13 +02:00
|
|
|
#include "td/utils/logging.h"
|
2020-09-14 16:04:45 +02:00
|
|
|
#include "td/utils/PathView.h"
|
2021-05-17 14:21:11 +02:00
|
|
|
#include "td/utils/SliceBuilder.h"
|
2020-06-17 02:04:36 +02:00
|
|
|
|
2020-09-30 21:02:39 +02:00
|
|
|
#if TD_PORT_WINDOWS
|
|
|
|
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
2020-09-14 22:32:13 +02:00
|
|
|
#include "td/utils/port/wstring_convert.h"
|
|
|
|
#endif
|
2020-09-30 21:02:39 +02:00
|
|
|
#endif
|
2020-09-14 22:32:13 +02:00
|
|
|
|
2020-09-30 21:02:39 +02:00
|
|
|
#if TD_PORT_WINDOWS
|
|
|
|
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
2020-09-14 22:32:13 +02:00
|
|
|
#include <shellapi.h>
|
|
|
|
#endif
|
2020-09-30 21:02:39 +02:00
|
|
|
#endif
|
2020-09-14 22:32:13 +02:00
|
|
|
|
2020-06-17 02:04:36 +02:00
|
|
|
namespace td {
|
|
|
|
|
2020-09-14 16:04:45 +02:00
|
|
|
void OptionParser::set_usage(Slice executable_name, Slice usage) {
|
|
|
|
PathView path_view(executable_name);
|
|
|
|
usage_ = PSTRING() << path_view.file_name() << " " << usage;
|
|
|
|
}
|
|
|
|
|
2020-06-17 02:04:36 +02:00
|
|
|
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) {
|
2020-06-24 13:47:36 +02:00
|
|
|
for (auto &option : options_) {
|
2020-06-26 01:24:13 +02:00
|
|
|
if ((short_key != '\0' && option.short_key == short_key) || (!long_key.empty() && long_key == option.long_key)) {
|
|
|
|
LOG(ERROR) << "Ignore duplicated option '" << (short_key == '\0' ? '-' : short_key) << "' '" << long_key << "'";
|
2020-06-24 13:47:36 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-17 02:04:36 +02:00
|
|
|
options_.push_back(Option{type, short_key, long_key.str(), description.str(), std::move(callback)});
|
|
|
|
}
|
|
|
|
|
2020-06-19 03:44:38 +02:00
|
|
|
void OptionParser::add_checked_option(char short_key, Slice long_key, Slice description,
|
|
|
|
std::function<Status(Slice)> callback) {
|
2020-06-17 02:04:36 +02:00
|
|
|
add_option(Option::Type::Arg, short_key, long_key, description, std::move(callback));
|
|
|
|
}
|
|
|
|
|
2020-06-19 03:44:38 +02:00
|
|
|
void OptionParser::add_checked_option(char short_key, Slice long_key, Slice description,
|
|
|
|
std::function<Status(void)> callback) {
|
2020-06-17 02:04:36 +02:00
|
|
|
add_option(Option::Type::NoArg, short_key, long_key, description,
|
2020-06-19 03:44:38 +02:00
|
|
|
[callback = std::move(callback)](Slice) { return callback(); });
|
|
|
|
}
|
|
|
|
|
|
|
|
void OptionParser::add_option(char short_key, Slice long_key, Slice description, std::function<void(Slice)> callback) {
|
|
|
|
add_option(Option::Type::Arg, short_key, long_key, description, [callback = std::move(callback)](Slice parameter) {
|
|
|
|
callback(parameter);
|
|
|
|
return Status::OK();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void OptionParser::add_option(char short_key, Slice long_key, Slice description, std::function<void(void)> callback) {
|
|
|
|
add_option(Option::Type::NoArg, short_key, long_key, description, [callback = std::move(callback)](Slice) {
|
|
|
|
callback();
|
|
|
|
return Status::OK();
|
|
|
|
});
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
|
|
|
|
2020-06-19 04:50:18 +02:00
|
|
|
void OptionParser::add_check(std::function<Status()> check) {
|
|
|
|
checks_.push_back(std::move(check));
|
|
|
|
}
|
|
|
|
|
2020-06-19 05:00:01 +02:00
|
|
|
Result<vector<char *>> OptionParser::run(int argc, char *argv[], int expected_non_option_count) {
|
2020-09-30 21:02:39 +02:00
|
|
|
#if TD_PORT_WINDOWS
|
|
|
|
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
2020-09-14 22:32:13 +02:00
|
|
|
LPWSTR *utf16_argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
|
|
|
if (utf16_argv == nullptr) {
|
|
|
|
return Status::Error("Failed to parse command line");
|
|
|
|
}
|
|
|
|
vector<string> args_storage(argc);
|
|
|
|
vector<char *> args(argc);
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
|
|
TRY_RESULT_ASSIGN(args_storage[i], from_wstring(utf16_argv[i]));
|
|
|
|
args[i] = &args_storage[i][0];
|
|
|
|
}
|
|
|
|
LocalFree(utf16_argv);
|
|
|
|
argv = &args[0];
|
2020-09-30 21:02:39 +02:00
|
|
|
#endif
|
2020-09-14 22:32:13 +02:00
|
|
|
#endif
|
|
|
|
|
2020-11-20 01:33:09 +01:00
|
|
|
return run_impl(argc, argv, expected_non_option_count);
|
|
|
|
}
|
|
|
|
|
|
|
|
Result<vector<char *>> OptionParser::run_impl(int argc, char *argv[], int expected_non_option_count) {
|
2022-02-09 15:05:27 +01:00
|
|
|
FlatHashMap<char, const Option *> short_options;
|
|
|
|
FlatHashMap<string, const Option *> long_options;
|
2020-06-17 02:04:36 +02:00
|
|
|
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
|
2020-09-14 22:32:13 +02:00
|
|
|
Slice long_arg(arg + 2);
|
2020-06-19 03:44:38 +02:00
|
|
|
Slice parameter;
|
2020-06-17 02:04:36 +02:00
|
|
|
auto equal_pos = long_arg.find('=');
|
|
|
|
bool has_equal = equal_pos != Slice::npos;
|
|
|
|
if (has_equal) {
|
2020-06-19 03:44:38 +02:00
|
|
|
parameter = long_arg.substr(equal_pos + 1);
|
2020-06-17 02:04:36 +02:00
|
|
|
long_arg = long_arg.substr(0, equal_pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto it = long_options.find(long_arg.str());
|
|
|
|
if (it == long_options.end()) {
|
2020-09-14 13:22:43 +02:00
|
|
|
return Status::Error(PSLICE() << "Option \"" << long_arg << "\" is unrecognized");
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
auto option = it->second;
|
|
|
|
switch (option->type) {
|
|
|
|
case Option::Type::NoArg:
|
|
|
|
if (has_equal) {
|
2020-09-14 13:22:43 +02:00
|
|
|
return Status::Error(PSLICE() << "Option \"" << long_arg << "\" must not have an argument");
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Option::Type::Arg:
|
|
|
|
if (!has_equal) {
|
|
|
|
if (++arg_pos == argc) {
|
2020-09-14 13:22:43 +02:00
|
|
|
return Status::Error(PSLICE() << "Option \"" << long_arg << "\" requires an argument");
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
2020-09-14 22:32:13 +02:00
|
|
|
parameter = Slice(argv[arg_pos]);
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
2020-06-19 03:44:38 +02:00
|
|
|
TRY_STATUS(option->arg_callback(parameter));
|
2020-06-17 02:04:36 +02:00
|
|
|
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()) {
|
2020-09-14 13:22:43 +02:00
|
|
|
return Status::Error(PSLICE() << "Option \"" << arg[opt_pos] << "\" is unrecognized");
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
auto option = it->second;
|
2020-06-19 03:44:38 +02:00
|
|
|
Slice parameter;
|
2020-06-17 02:04:36 +02:00
|
|
|
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) {
|
2020-09-14 13:22:43 +02:00
|
|
|
return Status::Error(PSLICE() << "Option \"" << arg[opt_pos] << "\" requires an argument");
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
2020-09-14 22:32:13 +02:00
|
|
|
parameter = Slice(argv[arg_pos]);
|
2020-06-17 02:04:36 +02:00
|
|
|
} else {
|
2020-09-14 22:32:13 +02:00
|
|
|
parameter = Slice(arg + opt_pos + 1);
|
2020-06-19 03:44:38 +02:00
|
|
|
opt_pos += parameter.size();
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
2020-06-19 03:44:38 +02:00
|
|
|
TRY_STATUS(option->arg_callback(parameter));
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-19 05:00:01 +02:00
|
|
|
if (expected_non_option_count >= 0 && non_options.size() != static_cast<size_t>(expected_non_option_count)) {
|
|
|
|
if (expected_non_option_count == 0) {
|
|
|
|
return Status::Error("Unexpected non-option parameters specified");
|
|
|
|
}
|
|
|
|
if (non_options.size() > static_cast<size_t>(expected_non_option_count)) {
|
2022-01-24 13:01:35 +01:00
|
|
|
return Status::Error("Too many non-option parameters specified");
|
2020-06-19 05:00:01 +02:00
|
|
|
} else {
|
|
|
|
return Status::Error("Too few non-option parameters specified");
|
|
|
|
}
|
|
|
|
}
|
2020-06-19 04:50:18 +02:00
|
|
|
for (auto &check : checks_) {
|
|
|
|
TRY_STATUS(check());
|
|
|
|
}
|
2020-06-17 02:04:36 +02:00
|
|
|
|
|
|
|
return std::move(non_options);
|
|
|
|
}
|
|
|
|
|
|
|
|
StringBuilder &operator<<(StringBuilder &sb, const OptionParser &o) {
|
2020-09-14 16:04:45 +02:00
|
|
|
if (!o.usage_.empty()) {
|
|
|
|
sb << "Usage: " << o.usage_ << "\n\n";
|
|
|
|
}
|
2020-06-17 05:09:53 +02:00
|
|
|
if (!o.description_.empty()) {
|
|
|
|
sb << o.description_ << ". ";
|
|
|
|
}
|
|
|
|
sb << "Options:\n";
|
|
|
|
|
|
|
|
size_t max_length = 0;
|
|
|
|
for (auto &opt : o.options_) {
|
2020-09-14 13:33:42 +02:00
|
|
|
size_t length = 2;
|
|
|
|
if (!opt.long_key.empty()) {
|
|
|
|
length += 4 + opt.long_key.size();
|
|
|
|
}
|
2020-06-17 05:09:53 +02:00
|
|
|
if (opt.type != OptionParser::Option::Type::NoArg) {
|
2020-10-31 23:28:58 +01:00
|
|
|
length += 6;
|
2020-06-17 05:09:53 +02:00
|
|
|
}
|
|
|
|
if (length > max_length) {
|
|
|
|
max_length = length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
max_length++;
|
|
|
|
|
2020-06-17 02:04:36 +02:00
|
|
|
for (auto &opt : o.options_) {
|
2020-06-17 04:35:55 +02:00
|
|
|
bool has_short_key = opt.short_key != '\0';
|
2020-06-17 05:09:53 +02:00
|
|
|
sb << " ";
|
|
|
|
size_t length = max_length;
|
2020-06-17 04:35:55 +02:00
|
|
|
if (has_short_key) {
|
2020-06-17 05:09:53 +02:00
|
|
|
sb << '-' << opt.short_key;
|
2020-09-14 13:33:42 +02:00
|
|
|
} else {
|
|
|
|
sb << " ";
|
2020-06-17 04:35:55 +02:00
|
|
|
}
|
2020-09-14 13:33:42 +02:00
|
|
|
length -= 2;
|
2020-06-17 02:04:36 +02:00
|
|
|
if (!opt.long_key.empty()) {
|
2020-06-17 04:35:55 +02:00
|
|
|
if (has_short_key) {
|
2020-06-17 05:09:53 +02:00
|
|
|
sb << ", ";
|
2020-09-14 13:33:42 +02:00
|
|
|
} else {
|
|
|
|
sb << " ";
|
2020-06-17 04:35:55 +02:00
|
|
|
}
|
|
|
|
sb << "--" << opt.long_key;
|
2020-09-14 13:33:42 +02:00
|
|
|
length -= 4 + opt.long_key.size();
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
|
|
|
if (opt.type != OptionParser::Option::Type::NoArg) {
|
2020-10-31 23:28:58 +01:00
|
|
|
sb << "=<arg>";
|
|
|
|
length -= 6;
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
2020-06-17 05:09:53 +02:00
|
|
|
sb << string(length, ' ') << opt.description;
|
2020-06-17 04:35:55 +02:00
|
|
|
sb << '\n';
|
2020-06-17 02:04:36 +02:00
|
|
|
}
|
|
|
|
return sb;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace td
|