blob: 5edae6dc33e7ee0bce1bb28c13ee4140ad8d8e44 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#pragma once
#include <iostream>
#include <map>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
static_assert(__cplusplus >= 201701, "compiler need to support c++17 or newer");
namespace doris::cloud {
/**
* This class implements arguments parser, it accepts both short '-arg' and
* long '--argument' arguments
*
* simple::ArgParser parser(
* { // define args that will be used in program
* // arg_name default_value comment
* ArgParser::new_arg<bool> ("verion" , false , "print version or not") ,
* ArgParser::new_arg<long> ("duration", 10086 , "duration for the test in seconds"),
* ArgParser::new_arg<std::string>("flags" , "abcdef", "flags for testing") ,
* ArgParser::new_arg<double> ("pi" , 3.1415 , "pi") ,
* },
* true // skip unknown args if there is any
* );
*
* // parse/update from args
* parser.parse(argc, argv);
* // to use args, types must be specified when access args
* if (parser.get<bool>(version)) print_version();
* ...
*
*/
class ArgParser {
public:
using arg_map_t =
std::map<std::string,
std::tuple<std::variant<std::string, long, double, bool>, std::string>>;
using arg_t = arg_map_t::value_type;
/**
* Create a key-value pair of argument, used in initialization
* TODO: template arg pack for extension
*
* @param name argument name
* @val argument value, for default
* @comment comment for the argument
* @return an arg_t, internal element of arg_map_t
*/
template <typename T>
static arg_t new_arg(const std::string& name, const T& val, const std::string& comment = "") {
compile_time_check_type<T>();
return arg_t {name, {T {val}, comment}};
}
/**
* Print given args
*/
static void print(const arg_map_t& args) {
std::cout << "args: ";
for (const auto& i : args) {
std::cout << "{" << i.first << "=";
if (std::get_if<std::string>(&std::get<0>(i.second))) {
std::cout << std::get<std::string>(std::get<0>(i.second));
} else if (std::get_if<bool>(&std::get<0>(i.second))) {
std::cout << (std::get<bool>(std::get<0>(i.second)) ? "true" : "false");
} else if (std::get_if<double>(&std::get<0>(i.second))) {
std::cout << std::get<double>(std::get<0>(i.second));
} else if (std::get_if<long>(&std::get<0>(i.second))) {
std::cout << std::get<long>(std::get<0>(i.second));
} else { // not defined type
std::cout << "unknown type of " << i.first;
}
std::cout << "; " << std::get<1>(i.second) << "}, ";
}
std::cout << std::endl;
}
template <typename T>
constexpr static void compile_time_check_type() {
static_assert(std::is_same_v<std::string, T> || std::is_same_v<long, T> ||
std::is_same_v<double, T> || std::is_same_v<bool, T>,
"only std::string, long, double, and bool are allowed");
}
/**
* Initialize and declare arguments
*
* @param args arguments list that predefined
* @param skip_unknown_arg skip the unknown arguments when parse
*/
ArgParser(const arg_map_t& args = {}, bool skip_unknown_arg = false)
: args_(args), skip_unknown_(skip_unknown_arg) {}
/**
* Parse arguments
*
* @param argc number of arguments to parse
* @param argv arguments to parse,
* e.g. ["--argument=abc", "-v", "-has_xxx=false"]
* @out parse result, if nullptr, internal container will be used
* if out == nullptr and internal container is not initialized
* call to this function does not make sense
* @return error msg that encountered
*/
std::string parse(int argc, char const* const* argv, arg_map_t* out = nullptr) {
arg_map_t ret;
// copy
if (out != nullptr)
ret = *out;
else
ret = args_;
std::string msg;
std::vector<std::string> args;
args.reserve(argc);
for (int i = 0; i < argc; ++i) {
args.emplace_back(argv[i]);
auto& arg = args.back();
auto eq = arg.find('=');
// process boolean flags first
if (eq == std::string::npos) { // "--version" || "-version" || "--help"
auto k = arg.substr(1);
auto it = ret.find(k) == ret.end() ? ret.find(k.substr(1)) : ret.find(k);
if (it != ret.end() && std::get_if<bool>(&std::get<0>(it->second))) {
std::get<0>(it->second) = true;
continue;
}
}
if (arg.size() < 4 || arg[0] != '-' || arg[2] == '-' || eq == std::string::npos) {
// std::cerr << "invalid arg: " << arg << std::endl;
msg += "invalid arg: " + arg + "; ";
if (skip_unknown_)
continue;
else
return msg;
}
std::string k = arg.substr(1 + (arg[1] == '-'), eq - 1 - (arg[1] == '-'));
auto p = ret.find(k);
if (p == ret.end()) { // no such an arg
msg += "arg not supported: " + k + "; ";
if (skip_unknown_)
continue;
else
return msg;
}
std::string v = arg.substr(eq + 1);
// std::cout << arg << ", k: " << k << ", v: " << v << std::endl;
if (std::get_if<std::string>(&std::get<0>(p->second))) {
std::get<0>(p->second) = v;
} else if (std::get_if<bool>(&std::get<0>(p->second))) {
if (v[0] >= '0' && v[0] <= '9') {
std::get<0>(p->second) = !!(std::stol(v));
} else if (v.find("true") != std::string::npos && v.size() == 4) {
std::get<0>(p->second) = true;
} else if (v.find("false") != std::string::npos && v.size() == 5) {
std::get<0>(p->second) = false;
} else {
msg += "invalid arg " + arg + ", it should true or false. ";
if (skip_unknown_)
continue;
else
return msg;
}
} else { // number
std::get<0>(p->second) = std::stol(v);
}
}
if (out != nullptr)
*out = std::move(ret);
else
args_ = std::move(ret);
return msg;
}
auto& args() { return args_; }
void print() { print(args_); }
// std::remove_cvref_t is available since c++2a
template <typename T>
auto get(const std::string& name,
const std::remove_reference_t<std::remove_cv_t<T>>& default_ = {}) {
typedef std::remove_reference_t<std::remove_cv_t<T>> U;
compile_time_check_type<U>();
auto it = args_.find(name);
// avoid invalid cast
if (it == args_.end() || !std::get_if<U>(&std::get<0>(it->second))) {
return default_;
}
return std::get<U>(std::get<0>(it->second));
}
private:
arg_map_t args_;
bool skip_unknown_;
};
} // namespace doris::cloud