Files
rocm-systems/include/timemory/utility/argparse.hpp
T
Jonathan R. Madsen 9ef3800986 Hosttrace via Dyninst
- complete with ctest support
2021-08-06 13:08:57 -05:00

1125 lines
38 KiB
C++

// MIT License
//
// Copyright (c) 2020, The Regents of the University of California,
// through Lawrence Berkeley National Laboratory (subject to receipt of any
// required approvals from the U.S. Dept. of Energy). All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include "timemory/utility/macros.hpp"
#include "timemory/utility/types.hpp"
#include "timemory/utility/utility.hpp"
#include <algorithm>
#include <cassert>
#include <cstring>
#include <deque>
#include <functional>
#include <iomanip>
#include <iosfwd>
#include <list>
#include <map>
#include <numeric>
#include <regex>
#include <set>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace tim
{
namespace argparse
{
namespace helpers
{
//
//--------------------------------------------------------------------------------------//
//
static inline bool
not_is_space(int ch)
{
return std::isspace(ch) == 0;
}
//
//--------------------------------------------------------------------------------------//
//
static inline uint64_t
lcount(const std::string& s, bool (*f)(int) = not_is_space)
{
uint64_t c = 0;
for(size_t i = 0; i < s.length(); ++i, ++c)
{
if(f(s.at(i)))
break;
}
return c;
}
//
//--------------------------------------------------------------------------------------//
//
static inline std::string
ltrim(std::string s, bool (*f)(int) = not_is_space)
{
s.erase(s.begin(), std::find_if(s.begin(), s.end(), f));
return s;
}
//
//--------------------------------------------------------------------------------------//
//
static inline std::string
rtrim(std::string s, bool (*f)(int) = not_is_space)
{
s.erase(std::find_if(s.rbegin(), s.rend(), f).base(), s.end());
return s;
}
//
//--------------------------------------------------------------------------------------//
//
static inline std::string
trim(std::string s, bool (*f)(int) = not_is_space)
{
ltrim(s, f);
rtrim(s, f);
return s;
}
//
//--------------------------------------------------------------------------------------//
//
static inline char*
strdup(const char* s)
{
auto slen = strlen(s);
auto* result = new char[slen + 1];
if(result)
{
memcpy(result, s, slen * sizeof(char));
result[slen] = '\0';
return result;
}
return nullptr;
}
//
//--------------------------------------------------------------------------------------//
//
template <typename InputIt>
static inline std::string
join(InputIt begin, InputIt end, const std::string& separator = " ")
{
std::ostringstream ss;
if(begin != end)
{
ss << *begin++;
}
while(begin != end)
{
ss << separator;
ss << *begin++;
}
return ss.str();
}
//
//--------------------------------------------------------------------------------------//
//
static inline bool
is_numeric(const std::string& arg)
{
auto _nidx = arg.find_first_of("0123456789");
auto _oidx = arg.find_first_not_of("0123456789.Ee+-*/");
// must have number somewhere
if(_nidx == std::string::npos)
return false;
// if something other than number or scientific notation
if(_oidx != std::string::npos)
return false;
// numbers + possible scientific notation
return true;
}
//
//--------------------------------------------------------------------------------------//
//
static inline int
find_equiv(const std::string& s)
{
for(size_t i = 0; i < s.length(); ++i)
{
// if find graph symbol before equal, end search
// i.e. don't accept --asd)f=0 arguments
// but allow --asd_f and --asd-f arguments
if(std::ispunct(static_cast<int>(s[i])) != 0)
{
if(s[i] == '=')
{
return static_cast<int>(i);
}
if(s[i] == '_' || s[i] == '-')
{
continue;
}
return -1;
}
}
return -1;
}
//
//--------------------------------------------------------------------------------------//
//
static inline size_t
find_punct(const std::string& s)
{
size_t i;
for(i = 0; i < s.length(); ++i)
{
if((std::ispunct(static_cast<int>(s[i])) != 0) && s[i] != '-')
{
break;
}
}
return i;
}
//
//--------------------------------------------------------------------------------------//
//
namespace is_container_impl
{
//
template <typename T>
struct is_container : std::false_type
{};
//
template <typename... Args>
struct is_container<std::vector<Args...>> : std::true_type
{};
template <typename... Args>
struct is_container<std::set<Args...>> : std::true_type
{};
template <typename... Args>
struct is_container<std::deque<Args...>> : std::true_type
{};
template <typename... Args>
struct is_container<std::list<Args...>> : std::true_type
{};
//
template <typename T>
struct is_initializing_container : is_container<T>::type
{};
//
template <typename... Args>
struct is_initializing_container<std::initializer_list<Args...>> : std::true_type
{};
} // namespace is_container_impl
//
//--------------------------------------------------------------------------------------//
//
// type trait to utilize the implementation type traits as well as decay the type
template <typename T>
struct is_container
{
static constexpr bool const value =
is_container_impl::is_container<decay_t<T>>::value;
};
//
//--------------------------------------------------------------------------------------//
//
// type trait to utilize the implementation type traits as well as decay the type
template <typename T>
struct is_initializing_container
{
static constexpr bool const value =
is_container_impl::is_initializing_container<decay_t<T>>::value;
};
//
//--------------------------------------------------------------------------------------//
//
} // namespace helpers
//
//--------------------------------------------------------------------------------------//
//
// argument vector
//
//--------------------------------------------------------------------------------------//
//
/// \struct tim::argparse::argument_vector
/// \brief This class exists to simplify creating argument arrays compatible with execv*
/// routines and MPI_Comm_spawn/MPI_Comm_spawn_multiple
///
struct argument_vector : std::vector<std::string>
{
struct c_args : std::tuple<int, char**, std::string>
{
using base_type = std::tuple<int, char**, std::string>;
template <typename... Args>
c_args(Args&&... args)
: base_type(std::forward<Args>(args)...)
{}
auto& argc() { return std::get<0>(*this); }
auto& argv() { return std::get<1>(*this); }
auto& args() { return std::get<2>(*this); }
TIMEMORY_NODISCARD const auto& argc() const { return std::get<0>(*this); }
TIMEMORY_NODISCARD const auto& argv() const { return std::get<1>(*this); }
TIMEMORY_NODISCARD const auto& args() const { return std::get<2>(*this); }
void clear()
{
// uses comma operator to execute delete and return nullptr
for(int i = 0; i < argc(); ++i)
argv()[i] = (delete[] argv()[i], nullptr);
argv() = (delete[] argv(), nullptr);
}
};
using base_type = std::vector<std::string>;
using cargs_t = c_args;
template <typename... Args>
argument_vector(Args&&... args)
: base_type(std::forward<Args>(args)...)
{}
explicit argument_vector(int& argc, char**& argv);
explicit argument_vector(int& argc, const char**& argv);
explicit argument_vector(int& argc, const char* const*& argv);
TIMEMORY_NODISCARD cargs_t
get_execv(const base_type& _prepend, size_t _beg = 0,
size_t _end = std::numeric_limits<size_t>::max()) const;
TIMEMORY_NODISCARD cargs_t
get_execv(size_t _beg = 0, size_t _end = std::numeric_limits<size_t>::max()) const;
// helper function to free the memory created by get_execv, pass by reference
// so that we can set values to nullptr and avoid multiple delete errors
static void free_execv(cargs_t& itr) { itr.clear(); }
};
//
//--------------------------------------------------------------------------------------//
//
// argument parser
//
//--------------------------------------------------------------------------------------//
//
struct argument_parser
{
struct arg_result;
using this_type = argument_parser;
using result_type = arg_result;
using bool_func_t = std::function<bool(this_type&)>;
using action_func_t = std::function<void(this_type&)>;
using action_pair_t = std::pair<bool_func_t, action_func_t>;
using error_func_t = std::function<void(this_type&, arg_result&)>;
using known_args_t = std::tuple<arg_result, int, char**>;
using strvec_t = std::vector<std::string>;
using strset_t = std::set<std::string>;
//
//----------------------------------------------------------------------------------//
//
struct arg_result
{
arg_result() = default;
arg_result(std::string err) noexcept
: m_error(true)
, m_what(std::move(err))
{}
operator bool() const { return m_error; }
friend std::ostream& operator<<(std::ostream& os, const arg_result& dt);
TIMEMORY_NODISCARD const std::string& what() const { return m_what; }
private:
bool m_error = false;
std::string m_what = {};
};
//
//----------------------------------------------------------------------------------//
//
struct argument
{
using callback_t = std::function<void(void*&)>;
enum Position : int
{
LastArgument = -1,
IgnoreArgument = -2
};
enum Count : int
{
ANY = -1
};
~argument() { m_destroy(m_default); }
argument& name(const std::string& name)
{
m_names.push_back(name);
return *this;
}
argument& names(const std::vector<std::string>& names)
{
for(const auto& itr : names)
m_names.push_back(itr);
return *this;
}
argument& description(const std::string& description)
{
m_desc = description;
return *this;
}
argument& dtype(const std::string& _dtype)
{
m_dtype = _dtype;
return *this;
}
argument& required(bool req)
{
m_required = req;
return *this;
}
argument& position(int position)
{
if(position != Position::LastArgument)
{
// position + 1 because technically argument zero is the name of the
// executable
m_position = position + 1;
}
else
{
m_position = position;
}
return *this;
}
argument& max_count(int count)
{
m_max_count = count;
return *this;
}
argument& min_count(int count)
{
m_min_count = count;
return *this;
}
argument& count(int count)
{
m_count = count;
return *this;
}
template <typename T>
argument& set_default(const T& val)
{
m_found = true;
m_default_tidx = std::type_index{ typeid(decay_t<T>) };
m_callback = [&](void*& obj) {
m_destroy(obj);
if(!obj)
obj = (void*) new T{};
(*static_cast<T*>(obj)) = val;
};
m_destroy = [](void*& obj) {
if(obj)
delete static_cast<T*>(obj);
};
return *this;
}
template <typename T>
argument& set_default(T& val)
{
m_found = true;
m_default_tidx = std::type_index{ typeid(decay_t<T>) };
m_callback = [&](void*& obj) { obj = (void*) &val; };
return *this;
}
template <typename T>
argument& choices(const std::initializer_list<T>& _choices)
{
for(auto&& itr : _choices)
{
std::stringstream ss;
ss << itr;
m_choices.insert(ss.str());
}
return *this;
}
template <template <typename...> class ContainerT, typename T, typename... ExtraT,
typename ContT = ContainerT<T, ExtraT...>,
enable_if_t<helpers::is_container<ContT>::value> = 0>
argument& choices(const ContainerT<T, ExtraT...>& _choices)
{
for(auto&& itr : _choices)
{
std::stringstream ss;
ss << itr;
m_choices.insert(ss.str());
}
return *this;
}
template <typename ActionFuncT>
argument& action(ActionFuncT&& _func)
{
m_actions.push_back(std::forward<ActionFuncT>(_func));
return *this;
}
TIMEMORY_NODISCARD bool found() const { return m_found; }
template <typename T>
std::enable_if_t<helpers::is_container<T>::value, T> get()
{
T t = T{};
typename T::value_type vt;
for(auto& s : m_values)
{
std::istringstream in(s);
in >> vt;
t.insert(t.end(), vt);
}
if(m_values.empty() && m_default &&
m_default_tidx == std::type_index{ typeid(T) })
t = (*static_cast<T*>(m_default));
return t;
}
template <typename T>
std::enable_if_t<
!helpers::is_container<T>::value && !std::is_same<T, bool>::value, T>
get()
{
auto inp = get<std::string>();
std::istringstream iss{ inp };
T t = T{};
iss >> t >> std::ws;
if(inp.empty() && m_default && m_default_tidx == std::type_index{ typeid(T) })
t = (*static_cast<T*>(m_default));
return t;
}
template <typename T>
std::enable_if_t<std::is_same<T, bool>::value, T> get()
{
if(m_count == 0)
return found();
auto inp = get<std::string>();
if(inp.empty() && m_default && m_default_tidx == std::type_index{ typeid(T) })
return (*static_cast<T*>(m_default));
else if(inp.empty())
return found();
return get_bool(inp, found());
}
TIMEMORY_NODISCARD size_t size() const { return m_values.size(); }
TIMEMORY_NODISCARD std::string get_name() const
{
std::stringstream ss;
for(const auto& itr : m_names)
ss << "/" << itr;
return ss.str().substr(1);
}
private:
argument(const std::string& name, std::string desc, bool required = false)
: m_desc(std::move(desc))
, m_required(required)
{
m_names.push_back(name);
}
argument() = default;
arg_result check_choice(const std::string& value)
{
if(!m_choices.empty())
{
if(m_choices.find(value) == m_choices.end())
{
std::stringstream ss;
ss << "Invalid choice: '" << value << "'. Valid choices: ";
for(const auto& itr : m_choices)
ss << "'" << itr << "' ";
return arg_result(ss.str());
}
}
return arg_result{};
}
void execute_actions(argument_parser& p)
{
for(auto& itr : m_actions)
itr(p);
}
friend std::ostream& operator<<(std::ostream& os, const argument& arg)
{
std::stringstream ss;
ss << "names: ";
for(const auto& itr : arg.m_names)
ss << itr << " ";
ss << ", index: " << arg.m_index << ", count: " << arg.m_count
<< ", min count: " << arg.m_min_count << ", max count: " << arg.m_max_count
<< ", found: " << std::boolalpha << arg.m_found
<< ", required: " << std::boolalpha << arg.m_required
<< ", position: " << arg.m_position << ", values: ";
for(const auto& itr : arg.m_values)
ss << itr << " ";
os << ss.str();
return os;
}
friend struct argument_parser;
int m_position = Position::IgnoreArgument;
int m_count = Count::ANY;
int m_min_count = Count::ANY;
int m_max_count = Count::ANY;
std::vector<std::string> m_names = {};
std::string m_desc = {};
std::string m_dtype = {};
bool m_found = false;
bool m_required = false;
int m_index = -1;
std::type_index m_default_tidx = std::type_index{ typeid(void) };
void* m_default = nullptr;
callback_t m_callback = [](void*&) {};
callback_t m_destroy = [](void*&) {};
std::set<std::string> m_choices = {};
std::vector<std::string> m_values = {};
std::vector<action_func_t> m_actions = {};
};
//
//----------------------------------------------------------------------------------//
//
argument_parser(std::string desc)
: m_desc(std::move(desc))
{}
//
//----------------------------------------------------------------------------------//
//
argument& add_argument()
{
m_arguments.push_back({});
m_arguments.back().m_index = static_cast<int>(m_arguments.size()) - 1;
return m_arguments.back();
}
//
//----------------------------------------------------------------------------------//
//
argument& add_argument(const std::initializer_list<std::string>& _names,
const std::string& desc, bool req = false)
{
return add_argument().names(_names).description(desc).required(req);
}
//
//----------------------------------------------------------------------------------//
//
argument& add_argument(const std::vector<std::string>& _names,
const std::string& desc, bool req = false)
{
return add_argument().names(_names).description(desc).required(req);
}
//
//----------------------------------------------------------------------------------//
//
argument& add_positional_argument(const std::string& _name)
{
m_positional_arguments.push_back({});
auto& _entry = m_positional_arguments.back();
_entry.name(_name);
_entry.count(1);
_entry.m_index = m_positional_arguments.size();
return _entry;
}
//
//----------------------------------------------------------------------------------//
//
template <typename Tp>
arg_result get(size_t _idx, Tp& _value)
{
if(m_positional_values.find(_idx) == m_positional_values.end())
return arg_result{ "Positional value not found at index " +
std::to_string(_idx) };
if(_idx >= m_positional_arguments.size())
return arg_result{ "No positional argument was specified for index " +
std::to_string(_idx) };
_value = m_positional_arguments.at(_idx).get<Tp>();
return arg_result{};
}
//
//----------------------------------------------------------------------------------//
//
template <typename Tp>
arg_result get(const std::string& _name, Tp& _value)
{
// loop over parsed positional args
for(size_t i = 0; i < m_positional_values.size(); ++i)
{
if(i >= m_positional_arguments.size())
break;
// loop over added positional args
auto& itr = m_positional_arguments.at(i);
for(auto& nitr : itr.m_names)
{
if(nitr == _name)
return get(i, _value);
}
}
// not found, check if required
for(auto& itr : m_positional_arguments)
{
for(auto& nitr : itr.m_names)
{
if(nitr == _name)
{
if(itr.m_default &&
itr.m_default_tidx == std::type_index{ typeid(decay_t<Tp>) })
_value = (*static_cast<Tp*>(itr.m_default));
else if(itr.m_required)
return arg_result{
_name + " not parsed from the command line (required)"
};
return arg_result{};
}
}
}
return arg_result{ _name + " is not a named positional argument" };
}
//
//----------------------------------------------------------------------------------//
//
template <typename BoolFuncT, typename ActionFuncT>
this_type& add_action(BoolFuncT&& _b, ActionFuncT& _act)
{
m_actions.push_back(
{ std::forward<BoolFuncT>(_b), std::forward<ActionFuncT>(_act) });
return *this;
}
//
//----------------------------------------------------------------------------------//
//
template <typename ActionFuncT>
this_type& add_action(const std::string& _name, ActionFuncT& _act)
{
auto _b = [=](this_type& p) { return p.exists(_name); };
m_actions.push_back({ _b, std::forward<ActionFuncT>(_act) });
return *this;
}
//
//----------------------------------------------------------------------------------//
//
void print_help(const std::string& _extra = "");
//
//----------------------------------------------------------------------------------//
//
/// \fn arg_result parse_known_args(int argc, char** argv, const std::string& delim,
/// int verb)
/// \param[in,out] argc Number of arguments (i.e. # of command-line args)
/// \param[in,out] argv Array of strings (i.e. command-line)
/// \param[in] delim Delimiter which separates this argparser's opts from user's
/// arguments
/// \param[in] verb verbosity
///
/// \brief Basic variant of \ref parse_known_args which does not replace argc/argv
/// and does not provide an array of strings that it processed
///
known_args_t parse_known_args(int argc, char** argv, const std::string& _delim = "--",
int verbose_level = 0)
{
strvec_t _args{};
return parse_known_args(argc, argv, _args, _delim, verbose_level);
}
//
//----------------------------------------------------------------------------------//
//
arg_result parse_known_args(int* argc, char*** argv, const std::string& _delim = "--",
int verbose_level = 0)
{
strvec_t args{};
return parse_known_args(argc, argv, args, _delim, verbose_level);
}
//
//----------------------------------------------------------------------------------//
//
/// \fn arg_result parse_known_args(int* argc, char*** argv, const std::string& delim,
/// int verb)
/// \param[in,out] argc Pointer to number of arguments (i.e. # of command-line args)
/// \param[in,out] argv Pointer to array of strings (i.e. command-line)
/// \param[in] delim Delimiter which separates this argparser's opts from user's
/// arguments
/// \param[in] verb verbosity
///
/// \brief This variant calls \ref parse_known_args and replaces argc and argv with
/// the argv[0] + anything after delimiter (if the delimiter is provided). If the
/// delimiter does not exist, argc and argv are unchanged.
///
arg_result parse_known_args(int* argc, char*** argv, strvec_t& _args,
const std::string& _delim = "--", int verbose_level = 0);
//
//----------------------------------------------------------------------------------//
//
/// \fn arg_result parse_known_args(int argc, char** argv, strvec_t& args, const
/// std::string& delim, int verb)
/// \param[in,out] argc Number of arguments (i.e. # of command-line args)
/// \param[in,out] argv Array of strings (i.e. command-line)
/// \param[in,out] args Array of strings processed by this parser
/// \param[in] delim Delimiter which separates this argparser's opts from user's
/// arguments
/// \param[in] verb verbosity
///
/// \brief Parses all options until argv[argc-1] or delimiter is found.
/// Returns a tuple containing an argument error object (operator bool will return
/// true if there was an error) and the new argc and argv after the known arguments
/// have been processed. This is slightly different from the Python
/// argparse.ArgumentParser.parse_known_args: if the delimiter is not found, it will
/// not remove the arguments that it recognizes.
/// To distinguish this parsers options from user arguments, use the syntax:
///
/// ./<CMD> <PARSER_OPTIONS> -- <USER_ARGS>
///
/// And std::get<1>(...) on the return value will be the new argc.
/// and std::get<2>(...) on the return value will be the new argv.
/// Other valid usages:
///
/// ./<CMD> --help (will report this parser's help message)
/// ./<CMD> -- --help (will report the applications help message, if supported)
/// ./<CMD> <USER_ARGS>
/// ./<CMD> <PARSER_OPTIONS>
/// ./<CMD> <PARSER_OPTIONS> <USER_ARGS> (intermixed)
///
/// will not remove any of the known options.
/// In other words, this will remove all arguments after <CMD> until the first "--" if
/// reached and everything after the "--" will be placed in argv[1:]
///
known_args_t parse_known_args(int argc, char** argv, strvec_t& _args,
const std::string& _delim = "--",
int verbose_level = 0);
//
//----------------------------------------------------------------------------------//
//
template <typename... Args>
arg_result parse_args(Args&&... args)
{
return parse(std::forward<Args>(args)...);
}
//
//----------------------------------------------------------------------------------//
//
arg_result parse(int argc, char** argv, int verbose_level = 0)
{
std::vector<std::string> _args;
_args.reserve(argc);
for(int i = 0; i < argc; ++i)
_args.emplace_back((const char*) argv[i]);
return parse(_args, verbose_level);
}
//
//----------------------------------------------------------------------------------//
//
/// \fn arg_result parse(const std::vector<std::string>& args, int verb)
/// \param[in] args Array of strings (i.e. command-line arguments)
/// \param[in] verb Verbosity
///
/// \brief This is the primary function for parsing the command line arguments.
/// This is where the map of the options is built and the loop over the
/// arguments is performed.
arg_result parse(const std::vector<std::string>& _args, int verbose_level = 0);
//
//----------------------------------------------------------------------------------//
//
/// \fn argument& enable_help()
/// \brief Add a help command
argument& enable_help()
{
m_help_enabled = true;
return add_argument()
.names({ "-h", "-?", "--help" })
.description("Shows this page")
.count(0);
}
//
//----------------------------------------------------------------------------------//
//
/// \fn bool exists(const std::string& name) const
/// \brief Returns whether or not an option was found in the arguments. Only
/// useful after a call to \ref parse or \ref parse_known_args.
///
/// \code{.cpp}
///
/// int main(int argc, char** argv)
/// {
/// argument_parser p{ argv[0] };
/// p.add_argument()
/// .names({ "-h", "--help"})
/// .description("Help message")
/// .count(0);
///
/// auto ec = p.parse(argc, argv);
/// if(ec)
/// {
/// std::cerr << "Error: " << ec << std::endl;
/// exit(EXIT_FAILURE);
/// }
///
/// if(p.exists("help"))
/// {
/// p.print_help();
/// exit(EXIT_FAILURE);
/// }
///
/// // ...
/// }
/// \endcode
TIMEMORY_NODISCARD bool exists(const std::string& name) const
{
std::string n = helpers::ltrim(
name, [](int c) -> bool { return c != static_cast<int>('-'); });
auto itr = m_name_map.find(n);
if(itr != m_name_map.end())
return m_arguments[static_cast<size_t>(itr->second)].m_found;
return false;
}
//
//----------------------------------------------------------------------------------//
//
/// \fn T get(const std::string& name)
/// \tparam T Data type to convert the argument into
/// \param[in] name An identifier of the option
///
/// \brief Get the value(s) associated with an argument. If option, it should
/// be used in conjunction with \ref exists(name). Only useful after a call to \ref
/// parse or \ref parse_known_args.
///
/// \code{.cpp}
///
/// int main(int argc, char** argv)
/// {
/// argument_parser p{ argv[0] };
/// p.add_argument()
/// .names({ "-n", "--iterations"})
/// .description("Number of iterations")
/// .count(1);
///
/// // ... etc.
///
/// auto nitr = p.get<size_t>("iteration");
///
/// // ... etc.
/// }
/// \endcode
template <typename T>
T get(const std::string& name)
{
auto itr = m_name_map.find(name);
if(itr != m_name_map.end())
return m_arguments[static_cast<size_t>(itr->second)].get<T>();
return T{};
}
//
//----------------------------------------------------------------------------------//
//
int64_t get_count(const std::string& name)
{
auto itr = m_name_map.find(name);
if(itr != m_name_map.end())
return m_arguments[static_cast<size_t>(itr->second)].size();
return 0;
}
//
//----------------------------------------------------------------------------------//
//
int64_t get_positional_count() const { return m_positional_values.size(); }
//
//----------------------------------------------------------------------------------//
//
static int64_t get_count(argument& a) { return a.m_values.size(); }
//
//----------------------------------------------------------------------------------//
//
template <typename ErrorFuncT>
void on_error(ErrorFuncT&& _func)
{
on_error_sfinae(std::forward<ErrorFuncT>(_func), 0);
}
//
//----------------------------------------------------------------------------------//
//
void set_help_width(int _v) { m_width = _v; }
private:
//
//----------------------------------------------------------------------------------//
//
template <typename FuncT = std::function<bool(int, int)>>
arg_result check_count(argument& a, const std::string& _do_str = "111",
const FuncT& _func = std::not_equal_to<int>{})
{
int _sz = static_cast<int>(a.m_values.size());
int _cnt = a.m_count;
int _max = a.m_max_count;
int _min = a.m_min_count;
std::bitset<3> _do{ _do_str };
std::bitset<3> _checks;
_checks.reset(); // set all to false
// if <val> > ANY AND <val does not satisfies condition> -> true
_checks.set(0, _do.test(0) && _cnt > argument::Count::ANY && _func(_sz, _cnt));
_checks.set(1, _do.test(1) && _max > argument::Count::ANY && _sz > _max);
_checks.set(2, _do.test(2) && _min > argument::Count::ANY && _sz < _min);
// if no checks failed, return non-error
if(_checks.none())
return arg_result{};
// otherwise, compose an error message
std::stringstream msg;
msg << "Argument: " << a.get_name() << " failed to satisfy its argument count "
<< "requirements. Number of arguments: " << _sz << ".";
if(_checks.test(0))
{
msg << "\n[" << a.get_name() << "]> Requires exactly " << _cnt << " values.";
}
else
{
if(_checks.test(1))
{
msg << "\n[" << a.get_name() << "]> Requires less than " << _max + 1
<< " values.";
}
if(_checks.test(2))
{
msg << "\n[" << a.get_name() << "]> Requires more than " << _min - 1
<< " values.";
}
}
return arg_result(msg.str());
}
//
//----------------------------------------------------------------------------------//
//
template <typename FuncT = std::function<bool(int, int)>>
arg_result check_count(const std::string& name, const std::string& _do = "111",
const FuncT& _func = std::not_equal_to<int>{})
{
auto itr = m_name_map.find(name);
if(itr != m_name_map.end())
return check_count(m_arguments[static_cast<size_t>(itr->second)], _do, _func);
return arg_result{};
}
//
//----------------------------------------------------------------------------------//
//
template <typename ErrorFuncT>
auto on_error_sfinae(ErrorFuncT&& _func, int)
-> decltype(_func(std::declval<this_type&>(), std::declval<result_type>()),
void())
{
m_error_func = std::forward<ErrorFuncT>(_func);
}
//
//----------------------------------------------------------------------------------//
//
template <typename ErrorFuncT>
auto on_error_sfinae(ErrorFuncT&& _func, long)
-> decltype(_func(std::declval<result_type>()), void())
{
auto _wrap_func = [=](this_type&, result_type ret) { _func(ret); };
m_error_func = _wrap_func;
}
//
//----------------------------------------------------------------------------------//
//
arg_result begin_argument(const std::string& arg, bool longarg, int position);
arg_result add_value(const std::string& value, int location);
arg_result end_argument();
//
//----------------------------------------------------------------------------------//
//
private:
bool m_help_enabled = false;
int m_current = -1;
int m_width = 30;
std::string m_desc = {};
std::string m_bin = {};
error_func_t m_error_func = [](this_type&, const result_type&) {};
std::vector<argument> m_arguments = {};
std::map<int, int> m_positional_map = {};
std::map<std::string, int> m_name_map = {};
std::vector<action_pair_t> m_actions = {};
std::vector<argument> m_positional_arguments = {};
std::map<int, std::string> m_positional_values = {};
};
//
//--------------------------------------------------------------------------------------//
//
template <>
inline std::string
argument_parser::argument::get<std::string>()
{
using T = std::string;
if(m_values.empty() && m_default != nullptr &&
m_default_tidx == std::type_index{ typeid(T) })
return (*static_cast<T*>(m_default));
return helpers::join(m_values.begin(), m_values.end());
}
//
//--------------------------------------------------------------------------------------//
//
template <>
inline std::vector<std::string>
argument_parser::argument::get<std::vector<std::string>>()
{
using T = std::vector<std::string>;
if(m_values.empty() && m_default != nullptr &&
m_default_tidx == std::type_index{ typeid(T) })
return (*static_cast<T*>(m_default));
return m_values;
}
//
//--------------------------------------------------------------------------------------//
//
} // namespace argparse
} // namespace tim
#if defined(TIMEMORY_UTILITY_HEADER_MODE)
# include "timemory/utility/argparse.cpp"
#endif