1312 lines
25 KiB
C++
1312 lines
25 KiB
C++
|
/*
|
|||
|
|
|||
|
Copyright (c) 2014 Jarryd Beck
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
*/
|
|||
|
|
|||
|
#ifndef CXX_OPTS_HPP
|
|||
|
#define CXX_OPTS_HPP
|
|||
|
|
|||
|
#if defined(__GNUC__)
|
|||
|
#pragma GCC diagnostic push
|
|||
|
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
|
|||
|
#endif
|
|||
|
|
|||
|
#include <exception>
|
|||
|
#include <iostream>
|
|||
|
#include <map>
|
|||
|
#include <memory>
|
|||
|
#include <regex>
|
|||
|
#include <sstream>
|
|||
|
#include <string>
|
|||
|
#include <vector>
|
|||
|
|
|||
|
//when we ask cxxopts to use Unicode, help strings are processed using ICU,
|
|||
|
//which results in the correct lengths being computed for strings when they
|
|||
|
//are formatted for the help output
|
|||
|
//it is necessary to make sure that <unicode/unistr.h> can be found by the
|
|||
|
//compiler, and that icu-uc is linked in to the binary.
|
|||
|
|
|||
|
#ifdef CXXOPTS_USE_UNICODE
|
|||
|
#include <unicode/unistr.h>
|
|||
|
|
|||
|
namespace cxxopts
|
|||
|
{
|
|||
|
typedef icu::UnicodeString String;
|
|||
|
|
|||
|
inline
|
|||
|
String
|
|||
|
toLocalString(std::string s)
|
|||
|
{
|
|||
|
return icu::UnicodeString::fromUTF8(s);
|
|||
|
}
|
|||
|
|
|||
|
class UnicodeStringIterator : public
|
|||
|
std::iterator<std::forward_iterator_tag, int32_t>
|
|||
|
{
|
|||
|
public:
|
|||
|
|
|||
|
UnicodeStringIterator(const icu::UnicodeString* s, int32_t pos)
|
|||
|
: s(s)
|
|||
|
, i(pos)
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
value_type
|
|||
|
operator*() const
|
|||
|
{
|
|||
|
return s->char32At(i);
|
|||
|
}
|
|||
|
|
|||
|
bool
|
|||
|
operator==(const UnicodeStringIterator& rhs) const
|
|||
|
{
|
|||
|
return s == rhs.s && i == rhs.i;
|
|||
|
}
|
|||
|
|
|||
|
bool
|
|||
|
operator!=(const UnicodeStringIterator& rhs) const
|
|||
|
{
|
|||
|
return !(*this == rhs);
|
|||
|
}
|
|||
|
|
|||
|
UnicodeStringIterator&
|
|||
|
operator++()
|
|||
|
{
|
|||
|
++i;
|
|||
|
return *this;
|
|||
|
}
|
|||
|
|
|||
|
UnicodeStringIterator
|
|||
|
operator+(int32_t v)
|
|||
|
{
|
|||
|
return UnicodeStringIterator(s, i + v);
|
|||
|
}
|
|||
|
|
|||
|
private:
|
|||
|
const icu::UnicodeString* s;
|
|||
|
int32_t i;
|
|||
|
};
|
|||
|
|
|||
|
inline
|
|||
|
String&
|
|||
|
stringAppend(String&s, String a)
|
|||
|
{
|
|||
|
return s.append(std::move(a));
|
|||
|
}
|
|||
|
|
|||
|
inline
|
|||
|
String&
|
|||
|
stringAppend(String& s, int n, UChar32 c)
|
|||
|
{
|
|||
|
for (int i = 0; i != n; ++i)
|
|||
|
{
|
|||
|
s.append(c);
|
|||
|
}
|
|||
|
|
|||
|
return s;
|
|||
|
}
|
|||
|
|
|||
|
template <typename Iterator>
|
|||
|
String&
|
|||
|
stringAppend(String& s, Iterator begin, Iterator end)
|
|||
|
{
|
|||
|
while (begin != end)
|
|||
|
{
|
|||
|
s.append(*begin);
|
|||
|
++begin;
|
|||
|
}
|
|||
|
|
|||
|
return s;
|
|||
|
}
|
|||
|
|
|||
|
inline
|
|||
|
size_t
|
|||
|
stringLength(const String& s)
|
|||
|
{
|
|||
|
return s.length();
|
|||
|
}
|
|||
|
|
|||
|
inline
|
|||
|
std::string
|
|||
|
toUTF8String(const String& s)
|
|||
|
{
|
|||
|
std::string result;
|
|||
|
s.toUTF8String(result);
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
namespace std
|
|||
|
{
|
|||
|
cxxopts::UnicodeStringIterator
|
|||
|
begin(const icu::UnicodeString& s)
|
|||
|
{
|
|||
|
return cxxopts::UnicodeStringIterator(&s, 0);
|
|||
|
}
|
|||
|
|
|||
|
cxxopts::UnicodeStringIterator
|
|||
|
end(const icu::UnicodeString& s)
|
|||
|
{
|
|||
|
return cxxopts::UnicodeStringIterator(&s, s.length());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//ifdef CXXOPTS_USE_UNICODE
|
|||
|
#else
|
|||
|
|
|||
|
namespace cxxopts
|
|||
|
{
|
|||
|
typedef std::string String;
|
|||
|
|
|||
|
template <typename T>
|
|||
|
T
|
|||
|
toLocalString(T&& t)
|
|||
|
{
|
|||
|
return t;
|
|||
|
}
|
|||
|
|
|||
|
inline
|
|||
|
size_t
|
|||
|
stringLength(const String& s)
|
|||
|
{
|
|||
|
return s.length();
|
|||
|
}
|
|||
|
|
|||
|
inline
|
|||
|
String&
|
|||
|
stringAppend(String&s, String a)
|
|||
|
{
|
|||
|
return s.append(std::move(a));
|
|||
|
}
|
|||
|
|
|||
|
inline
|
|||
|
String&
|
|||
|
stringAppend(String& s, size_t n, char c)
|
|||
|
{
|
|||
|
return s.append(n, c);
|
|||
|
}
|
|||
|
|
|||
|
template <typename Iterator>
|
|||
|
String&
|
|||
|
stringAppend(String& s, Iterator begin, Iterator end)
|
|||
|
{
|
|||
|
return s.append(begin, end);
|
|||
|
}
|
|||
|
|
|||
|
template <typename T>
|
|||
|
std::string
|
|||
|
toUTF8String(T&& t)
|
|||
|
{
|
|||
|
return std::forward<T>(t);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
//ifdef CXXOPTS_USE_UNICODE
|
|||
|
#endif
|
|||
|
|
|||
|
namespace cxxopts
|
|||
|
{
|
|||
|
class Value : public std::enable_shared_from_this<Value>
|
|||
|
{
|
|||
|
public:
|
|||
|
|
|||
|
virtual void
|
|||
|
parse(const std::string& text) const = 0;
|
|||
|
|
|||
|
virtual void
|
|||
|
parse() const = 0;
|
|||
|
|
|||
|
virtual bool
|
|||
|
has_arg() const = 0;
|
|||
|
|
|||
|
virtual bool
|
|||
|
has_default() const = 0;
|
|||
|
|
|||
|
virtual bool
|
|||
|
has_implicit() const = 0;
|
|||
|
|
|||
|
virtual std::string
|
|||
|
get_default_value() const = 0;
|
|||
|
|
|||
|
virtual std::string
|
|||
|
get_implicit_value() const = 0;
|
|||
|
|
|||
|
virtual std::shared_ptr<Value>
|
|||
|
default_value(const std::string& value) = 0;
|
|||
|
|
|||
|
virtual std::shared_ptr<Value>
|
|||
|
implicit_value(const std::string& value) = 0;
|
|||
|
};
|
|||
|
|
|||
|
class OptionException : public std::exception
|
|||
|
{
|
|||
|
public:
|
|||
|
OptionException(const std::string& message)
|
|||
|
: m_message(message)
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
virtual const char*
|
|||
|
what() const noexcept
|
|||
|
{
|
|||
|
return m_message.c_str();
|
|||
|
}
|
|||
|
|
|||
|
private:
|
|||
|
std::string m_message;
|
|||
|
};
|
|||
|
|
|||
|
class OptionSpecException : public OptionException
|
|||
|
{
|
|||
|
public:
|
|||
|
|
|||
|
OptionSpecException(const std::string& message)
|
|||
|
: OptionException(message)
|
|||
|
{
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
class OptionParseException : public OptionException
|
|||
|
{
|
|||
|
public:
|
|||
|
OptionParseException(const std::string& message)
|
|||
|
: OptionException(message)
|
|||
|
{
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
class option_exists_error : public OptionSpecException
|
|||
|
{
|
|||
|
public:
|
|||
|
option_exists_error(const std::string& option)
|
|||
|
: OptionSpecException(u8"Option ‘" + option + u8"’ already exists")
|
|||
|
{
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
class invalid_option_format_error : public OptionSpecException
|
|||
|
{
|
|||
|
public:
|
|||
|
invalid_option_format_error(const std::string& format)
|
|||
|
: OptionSpecException(u8"Invalid option format ‘" + format + u8"’")
|
|||
|
{
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
class option_not_exists_exception : public OptionParseException
|
|||
|
{
|
|||
|
public:
|
|||
|
option_not_exists_exception(const std::string& option)
|
|||
|
: OptionParseException(u8"Option ‘" + option + u8"’ does not exist")
|
|||
|
{
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
class missing_argument_exception : public OptionParseException
|
|||
|
{
|
|||
|
public:
|
|||
|
missing_argument_exception(const std::string& option)
|
|||
|
: OptionParseException(u8"Option ‘" + option + u8"’ is missing an argument")
|
|||
|
{
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
class option_requires_argument_exception : public OptionParseException
|
|||
|
{
|
|||
|
public:
|
|||
|
option_requires_argument_exception(const std::string& option)
|
|||
|
: OptionParseException(u8"Option ‘" + option + u8"’ requires an argument")
|
|||
|
{
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
class option_not_has_argument_exception : public OptionParseException
|
|||
|
{
|
|||
|
public:
|
|||
|
option_not_has_argument_exception
|
|||
|
(
|
|||
|
const std::string& option,
|
|||
|
const std::string& arg
|
|||
|
)
|
|||
|
: OptionParseException(
|
|||
|
u8"Option ‘" + option + u8"’ does not take an argument, but argument‘"
|
|||
|
+ arg + "’ given")
|
|||
|
{
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
class option_not_present_exception : public OptionParseException
|
|||
|
{
|
|||
|
public:
|
|||
|
option_not_present_exception(const std::string& option)
|
|||
|
: OptionParseException(u8"Option ‘" + option + u8"’ not present")
|
|||
|
{
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
class argument_incorrect_type : public OptionParseException
|
|||
|
{
|
|||
|
public:
|
|||
|
argument_incorrect_type
|
|||
|
(
|
|||
|
const std::string& arg
|
|||
|
)
|
|||
|
: OptionParseException(
|
|||
|
u8"Argument ‘" + arg + u8"’ failed to parse"
|
|||
|
)
|
|||
|
{
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
namespace values
|
|||
|
{
|
|||
|
template <typename T>
|
|||
|
void
|
|||
|
parse_value(const std::string& text, T& value)
|
|||
|
{
|
|||
|
std::istringstream is(text);
|
|||
|
if (!(is >> value))
|
|||
|
{
|
|||
|
std::cerr << "cannot parse empty value" << std::endl;
|
|||
|
throw argument_incorrect_type(text);
|
|||
|
}
|
|||
|
|
|||
|
if (is.rdbuf()->in_avail() != 0)
|
|||
|
{
|
|||
|
throw argument_incorrect_type(text);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
template <typename T>
|
|||
|
void
|
|||
|
parse_value(const std::string& text, std::vector<T>& value)
|
|||
|
{
|
|||
|
T v;
|
|||
|
parse_value(text, v);
|
|||
|
value.push_back(v);
|
|||
|
}
|
|||
|
|
|||
|
inline
|
|||
|
void
|
|||
|
parse_value(const std::string& /*text*/, bool& value)
|
|||
|
{
|
|||
|
//TODO recognise on, off, yes, no, enable, disable
|
|||
|
//so that we can write --long=yes explicitly
|
|||
|
value = true;
|
|||
|
}
|
|||
|
|
|||
|
inline
|
|||
|
void
|
|||
|
parse_value(const std::string& text, std::string& value)
|
|||
|
{
|
|||
|
value = text;
|
|||
|
}
|
|||
|
|
|||
|
template <typename T>
|
|||
|
struct value_has_arg
|
|||
|
{
|
|||
|
static constexpr bool value = true;
|
|||
|
};
|
|||
|
|
|||
|
template <>
|
|||
|
struct value_has_arg<bool>
|
|||
|
{
|
|||
|
static constexpr bool value = false;
|
|||
|
};
|
|||
|
|
|||
|
template <typename T>
|
|||
|
class standard_value : public Value
|
|||
|
{
|
|||
|
public:
|
|||
|
standard_value()
|
|||
|
: m_result(std::make_shared<T>())
|
|||
|
, m_store(m_result.get())
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
standard_value(T* t)
|
|||
|
: m_store(t)
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
parse(const std::string& text) const
|
|||
|
{
|
|||
|
if (m_implicit && text.empty())
|
|||
|
{
|
|||
|
parse_value(m_implicit_value, *m_store);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
parse_value(text, *m_store);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
parse() const
|
|||
|
{
|
|||
|
parse_value(m_default_value, *m_store);
|
|||
|
}
|
|||
|
|
|||
|
bool
|
|||
|
has_arg() const
|
|||
|
{
|
|||
|
return value_has_arg<T>::value;
|
|||
|
}
|
|||
|
|
|||
|
bool
|
|||
|
has_default() const
|
|||
|
{
|
|||
|
return m_default;
|
|||
|
}
|
|||
|
|
|||
|
bool
|
|||
|
has_implicit() const
|
|||
|
{
|
|||
|
return m_implicit;
|
|||
|
}
|
|||
|
|
|||
|
virtual std::shared_ptr<Value>
|
|||
|
default_value(const std::string& value){
|
|||
|
m_default = true;
|
|||
|
m_default_value = value;
|
|||
|
return shared_from_this();
|
|||
|
}
|
|||
|
|
|||
|
virtual std::shared_ptr<Value>
|
|||
|
implicit_value(const std::string& value){
|
|||
|
m_implicit = true;
|
|||
|
m_implicit_value = value;
|
|||
|
return shared_from_this();
|
|||
|
}
|
|||
|
|
|||
|
std::string
|
|||
|
get_default_value() const
|
|||
|
{
|
|||
|
return m_default_value;
|
|||
|
}
|
|||
|
|
|||
|
std::string
|
|||
|
get_implicit_value() const
|
|||
|
{
|
|||
|
return m_implicit_value;
|
|||
|
}
|
|||
|
|
|||
|
const T&
|
|||
|
get() const
|
|||
|
{
|
|||
|
if (m_store == nullptr)
|
|||
|
{
|
|||
|
return *m_result;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
return *m_store;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected:
|
|||
|
std::shared_ptr<T> m_result;
|
|||
|
T* m_store;
|
|||
|
bool m_default = false;
|
|||
|
std::string m_default_value;
|
|||
|
bool m_implicit = false;
|
|||
|
std::string m_implicit_value;
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
template <typename T>
|
|||
|
std::shared_ptr<Value>
|
|||
|
value()
|
|||
|
{
|
|||
|
return std::make_shared<values::standard_value<T>>();
|
|||
|
}
|
|||
|
|
|||
|
template <typename T>
|
|||
|
std::shared_ptr<Value>
|
|||
|
value(T& t)
|
|||
|
{
|
|||
|
return std::make_shared<values::standard_value<T>>(&t);
|
|||
|
}
|
|||
|
|
|||
|
class OptionAdder;
|
|||
|
|
|||
|
class OptionDetails
|
|||
|
{
|
|||
|
public:
|
|||
|
OptionDetails
|
|||
|
(
|
|||
|
const String& description,
|
|||
|
std::shared_ptr<const Value> value
|
|||
|
)
|
|||
|
: m_desc(description)
|
|||
|
, m_value(value)
|
|||
|
, m_count(0)
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
const String&
|
|||
|
description() const
|
|||
|
{
|
|||
|
return m_desc;
|
|||
|
}
|
|||
|
|
|||
|
bool
|
|||
|
has_arg() const
|
|||
|
{
|
|||
|
return m_value->has_arg();
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
parse(const std::string& text)
|
|||
|
{
|
|||
|
m_value->parse(text);
|
|||
|
++m_count;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
parse_default()
|
|||
|
{
|
|||
|
m_value->parse();
|
|||
|
++m_count;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
count() const
|
|||
|
{
|
|||
|
return m_count;
|
|||
|
}
|
|||
|
|
|||
|
const Value& value() const {
|
|||
|
return *m_value;
|
|||
|
}
|
|||
|
|
|||
|
template <typename T>
|
|||
|
const T&
|
|||
|
as() const
|
|||
|
{
|
|||
|
#ifdef CXXOPTS_NO_RTTI
|
|||
|
return static_cast<const values::standard_value<T>&>(*m_value).get();
|
|||
|
#else
|
|||
|
return dynamic_cast<const values::standard_value<T>&>(*m_value).get();
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
private:
|
|||
|
String m_desc;
|
|||
|
std::shared_ptr<const Value> m_value;
|
|||
|
int m_count;
|
|||
|
};
|
|||
|
|
|||
|
struct HelpOptionDetails
|
|||
|
{
|
|||
|
std::string s;
|
|||
|
std::string l;
|
|||
|
String desc;
|
|||
|
bool has_arg;
|
|||
|
bool has_default;
|
|||
|
std::string default_value;
|
|||
|
bool has_implicit;
|
|||
|
std::string implicit_value;
|
|||
|
std::string arg_help;
|
|||
|
};
|
|||
|
|
|||
|
struct HelpGroupDetails
|
|||
|
{
|
|||
|
std::string name;
|
|||
|
std::string description;
|
|||
|
std::vector<HelpOptionDetails> options;
|
|||
|
};
|
|||
|
|
|||
|
class Options
|
|||
|
{
|
|||
|
public:
|
|||
|
|
|||
|
Options(std::string program, std::string help_string = "")
|
|||
|
: m_program(std::move(program))
|
|||
|
, m_help_string(toLocalString(std::move(help_string)))
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
inline
|
|||
|
void
|
|||
|
parse(int& argc, char**& argv);
|
|||
|
|
|||
|
inline
|
|||
|
OptionAdder
|
|||
|
add_options(std::string group = "");
|
|||
|
|
|||
|
inline
|
|||
|
void
|
|||
|
add_option
|
|||
|
(
|
|||
|
const std::string& group,
|
|||
|
const std::string& s,
|
|||
|
const std::string& l,
|
|||
|
std::string desc,
|
|||
|
std::shared_ptr<const Value> value,
|
|||
|
std::string arg_help
|
|||
|
);
|
|||
|
|
|||
|
int
|
|||
|
count(const std::string& o) const
|
|||
|
{
|
|||
|
auto iter = m_options.find(o);
|
|||
|
if (iter == m_options.end())
|
|||
|
{
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
return iter->second->count();
|
|||
|
}
|
|||
|
|
|||
|
const OptionDetails&
|
|||
|
operator[](const std::string& option) const
|
|||
|
{
|
|||
|
auto iter = m_options.find(option);
|
|||
|
|
|||
|
if (iter == m_options.end())
|
|||
|
{
|
|||
|
throw option_not_present_exception(option);
|
|||
|
}
|
|||
|
|
|||
|
return *iter->second;
|
|||
|
}
|
|||
|
|
|||
|
//parse positional arguments into the given option
|
|||
|
inline
|
|||
|
void
|
|||
|
parse_positional(std::string option);
|
|||
|
|
|||
|
inline
|
|||
|
std::string
|
|||
|
help(const std::vector<std::string>& groups = {""}) const;
|
|||
|
|
|||
|
inline
|
|||
|
const std::vector<std::string>
|
|||
|
groups() const;
|
|||
|
|
|||
|
inline
|
|||
|
const HelpGroupDetails&
|
|||
|
group_help(const std::string& group) const;
|
|||
|
|
|||
|
private:
|
|||
|
|
|||
|
inline
|
|||
|
void
|
|||
|
add_one_option
|
|||
|
(
|
|||
|
const std::string& option,
|
|||
|
std::shared_ptr<OptionDetails> details
|
|||
|
);
|
|||
|
|
|||
|
inline
|
|||
|
bool
|
|||
|
consume_positional(std::string a);
|
|||
|
|
|||
|
inline
|
|||
|
void
|
|||
|
add_to_option(const std::string& option, const std::string& arg);
|
|||
|
|
|||
|
inline
|
|||
|
void
|
|||
|
parse_option
|
|||
|
(
|
|||
|
std::shared_ptr<OptionDetails> value,
|
|||
|
const std::string& name,
|
|||
|
const std::string& arg = ""
|
|||
|
);
|
|||
|
|
|||
|
inline
|
|||
|
void
|
|||
|
checked_parse_arg
|
|||
|
(
|
|||
|
int argc,
|
|||
|
char* argv[],
|
|||
|
int& current,
|
|||
|
std::shared_ptr<OptionDetails> value,
|
|||
|
const std::string& name
|
|||
|
);
|
|||
|
|
|||
|
inline
|
|||
|
String
|
|||
|
help_one_group(const std::string& group) const;
|
|||
|
|
|||
|
std::string m_program;
|
|||
|
String m_help_string;
|
|||
|
|
|||
|
std::map<std::string, std::shared_ptr<OptionDetails>> m_options;
|
|||
|
std::string m_positional;
|
|||
|
|
|||
|
//mapping from groups to help options
|
|||
|
std::map<std::string, HelpGroupDetails> m_help;
|
|||
|
};
|
|||
|
|
|||
|
class OptionAdder
|
|||
|
{
|
|||
|
public:
|
|||
|
|
|||
|
OptionAdder(Options& options, std::string group)
|
|||
|
: m_options(options), m_group(std::move(group))
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
inline
|
|||
|
OptionAdder&
|
|||
|
operator()
|
|||
|
(
|
|||
|
const std::string& opts,
|
|||
|
const std::string& desc,
|
|||
|
std::shared_ptr<const Value> value
|
|||
|
= ::cxxopts::value<bool>(),
|
|||
|
std::string arg_help = ""
|
|||
|
);
|
|||
|
|
|||
|
private:
|
|||
|
Options& m_options;
|
|||
|
std::string m_group;
|
|||
|
};
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
namespace cxxopts
|
|||
|
{
|
|||
|
|
|||
|
namespace
|
|||
|
{
|
|||
|
|
|||
|
constexpr int OPTION_LONGEST = 30;
|
|||
|
constexpr int OPTION_DESC_GAP = 2;
|
|||
|
|
|||
|
std::basic_regex<char> option_matcher
|
|||
|
("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([a-zA-Z]+)");
|
|||
|
|
|||
|
std::basic_regex<char> option_specifier
|
|||
|
("(([a-zA-Z]),)?([a-zA-Z0-9][-_a-zA-Z0-9]+)");
|
|||
|
|
|||
|
String
|
|||
|
format_option
|
|||
|
(
|
|||
|
const HelpOptionDetails& o
|
|||
|
)
|
|||
|
{
|
|||
|
auto& s = o.s;
|
|||
|
auto& l = o.l;
|
|||
|
|
|||
|
String result = " ";
|
|||
|
|
|||
|
if (s.size() > 0)
|
|||
|
{
|
|||
|
result += "-" + toLocalString(s) + ",";
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
result += " ";
|
|||
|
}
|
|||
|
|
|||
|
if (l.size() > 0)
|
|||
|
{
|
|||
|
result += " --" + toLocalString(l);
|
|||
|
}
|
|||
|
|
|||
|
if (o.has_arg)
|
|||
|
{
|
|||
|
auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg";
|
|||
|
|
|||
|
if (o.has_implicit)
|
|||
|
{
|
|||
|
result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
result += " " + arg;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
String
|
|||
|
format_description
|
|||
|
(
|
|||
|
const HelpOptionDetails& o,
|
|||
|
size_t start,
|
|||
|
size_t width
|
|||
|
)
|
|||
|
{
|
|||
|
auto desc = o.desc;
|
|||
|
|
|||
|
if (o.has_default)
|
|||
|
{
|
|||
|
desc += toLocalString(" (default:" + o.default_value + ")");
|
|||
|
}
|
|||
|
|
|||
|
String result;
|
|||
|
|
|||
|
auto current = std::begin(desc);
|
|||
|
auto startLine = current;
|
|||
|
auto lastSpace = current;
|
|||
|
|
|||
|
auto size = size_t{};
|
|||
|
|
|||
|
while (current != std::end(desc))
|
|||
|
{
|
|||
|
if (*current == ' ')
|
|||
|
{
|
|||
|
lastSpace = current;
|
|||
|
}
|
|||
|
|
|||
|
if (size > width)
|
|||
|
{
|
|||
|
if (lastSpace == startLine)
|
|||
|
{
|
|||
|
stringAppend(result, startLine, current + 1);
|
|||
|
stringAppend(result, "\n");
|
|||
|
stringAppend(result, start, ' ');
|
|||
|
startLine = current + 1;
|
|||
|
lastSpace = startLine;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
stringAppend(result, startLine, lastSpace);
|
|||
|
stringAppend(result, "\n");
|
|||
|
stringAppend(result, start, ' ');
|
|||
|
startLine = lastSpace + 1;
|
|||
|
}
|
|||
|
size = 0;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
++size;
|
|||
|
}
|
|||
|
|
|||
|
++current;
|
|||
|
}
|
|||
|
|
|||
|
//append whatever is left
|
|||
|
stringAppend(result, startLine, current);
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
OptionAdder
|
|||
|
Options::add_options(std::string group)
|
|||
|
{
|
|||
|
return OptionAdder(*this, std::move(group));
|
|||
|
}
|
|||
|
|
|||
|
OptionAdder&
|
|||
|
OptionAdder::operator()
|
|||
|
(
|
|||
|
const std::string& opts,
|
|||
|
const std::string& desc,
|
|||
|
std::shared_ptr<const Value> value,
|
|||
|
std::string arg_help
|
|||
|
)
|
|||
|
{
|
|||
|
std::match_results<const char*> result;
|
|||
|
std::regex_match(opts.c_str(), result, option_specifier);
|
|||
|
|
|||
|
if (result.empty())
|
|||
|
{
|
|||
|
throw invalid_option_format_error(opts);
|
|||
|
}
|
|||
|
|
|||
|
const auto& s = result[2];
|
|||
|
const auto& l = result[3];
|
|||
|
|
|||
|
m_options.add_option(m_group, s.str(), l.str(), desc, value,
|
|||
|
std::move(arg_help));
|
|||
|
|
|||
|
return *this;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
Options::parse_option
|
|||
|
(
|
|||
|
std::shared_ptr<OptionDetails> value,
|
|||
|
const std::string& /*name*/,
|
|||
|
const std::string& arg
|
|||
|
)
|
|||
|
{
|
|||
|
value->parse(arg);
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
Options::checked_parse_arg
|
|||
|
(
|
|||
|
int argc,
|
|||
|
char* argv[],
|
|||
|
int& current,
|
|||
|
std::shared_ptr<OptionDetails> value,
|
|||
|
const std::string& name
|
|||
|
)
|
|||
|
{
|
|||
|
if (current + 1 >= argc)
|
|||
|
{
|
|||
|
if (value->value().has_implicit())
|
|||
|
{
|
|||
|
parse_option(value, name, "");
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
throw missing_argument_exception(name);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (argv[current + 1][0] == '-' && value->value().has_implicit())
|
|||
|
{
|
|||
|
parse_option(value, name, "");
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
parse_option(value, name, argv[current + 1]);
|
|||
|
++current;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
Options::add_to_option(const std::string& option, const std::string& arg)
|
|||
|
{
|
|||
|
auto iter = m_options.find(option);
|
|||
|
|
|||
|
if (iter == m_options.end())
|
|||
|
{
|
|||
|
throw option_not_exists_exception(option);
|
|||
|
}
|
|||
|
|
|||
|
parse_option(iter->second, option, arg);
|
|||
|
}
|
|||
|
|
|||
|
bool
|
|||
|
Options::consume_positional(std::string a)
|
|||
|
{
|
|||
|
if (m_positional.size() > 0)
|
|||
|
{
|
|||
|
add_to_option(m_positional, a);
|
|||
|
return true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
Options::parse_positional(std::string option)
|
|||
|
{
|
|||
|
m_positional = std::move(option);
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
Options::parse(int& argc, char**& argv)
|
|||
|
{
|
|||
|
int current = 1;
|
|||
|
|
|||
|
int nextKeep = 1;
|
|||
|
|
|||
|
while (current != argc)
|
|||
|
{
|
|||
|
std::match_results<const char*> result;
|
|||
|
std::regex_match(argv[current], result, option_matcher);
|
|||
|
|
|||
|
if (result.empty())
|
|||
|
{
|
|||
|
//not a flag
|
|||
|
|
|||
|
//if true is returned here then it was consumed, otherwise it is
|
|||
|
//ignored
|
|||
|
if (consume_positional(argv[current]))
|
|||
|
{
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
argv[nextKeep] = argv[current];
|
|||
|
++nextKeep;
|
|||
|
}
|
|||
|
//if we return from here then it was parsed successfully, so continue
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//short or long option?
|
|||
|
if (result[4].length() != 0)
|
|||
|
{
|
|||
|
const std::string& s = result[4];
|
|||
|
|
|||
|
for (std::size_t i = 0; i != s.size(); ++i)
|
|||
|
{
|
|||
|
std::string name(1, s[i]);
|
|||
|
auto iter = m_options.find(name);
|
|||
|
|
|||
|
if (iter == m_options.end())
|
|||
|
{
|
|||
|
throw option_not_exists_exception(name);
|
|||
|
}
|
|||
|
|
|||
|
auto value = iter->second;
|
|||
|
|
|||
|
//if no argument then just add it
|
|||
|
if (!value->has_arg())
|
|||
|
{
|
|||
|
parse_option(value, name);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//it must be the last argument
|
|||
|
if (i + 1 == s.size())
|
|||
|
{
|
|||
|
checked_parse_arg(argc, argv, current, value, name);
|
|||
|
}
|
|||
|
else if (value->value().has_implicit())
|
|||
|
{
|
|||
|
parse_option(value, name, "");
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//error
|
|||
|
throw option_requires_argument_exception(name);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else if (result[1].length() != 0)
|
|||
|
{
|
|||
|
const std::string& name = result[1];
|
|||
|
|
|||
|
auto iter = m_options.find(name);
|
|||
|
|
|||
|
if (iter == m_options.end())
|
|||
|
{
|
|||
|
throw option_not_exists_exception(name);
|
|||
|
}
|
|||
|
|
|||
|
auto opt = iter->second;
|
|||
|
|
|||
|
//equals provided for long option?
|
|||
|
if (result[3].length() != 0)
|
|||
|
{
|
|||
|
//parse the option given
|
|||
|
|
|||
|
//but if it doesn't take an argument, this is an error
|
|||
|
if (!opt->has_arg())
|
|||
|
{
|
|||
|
throw option_not_has_argument_exception(name, result[3]);
|
|||
|
}
|
|||
|
|
|||
|
parse_option(opt, name, result[3]);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (opt->has_arg())
|
|||
|
{
|
|||
|
//parse the next argument
|
|||
|
checked_parse_arg(argc, argv, current, opt, name);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//parse with empty argument
|
|||
|
parse_option(opt, name);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
++current;
|
|||
|
}
|
|||
|
|
|||
|
for (auto& opt : m_options)
|
|||
|
{
|
|||
|
auto& detail = opt.second;
|
|||
|
auto& value = detail->value();
|
|||
|
|
|||
|
if(!detail->count() && value.has_default()){
|
|||
|
detail->parse_default();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
argc = nextKeep;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
Options::add_option
|
|||
|
(
|
|||
|
const std::string& group,
|
|||
|
const std::string& s,
|
|||
|
const std::string& l,
|
|||
|
std::string desc,
|
|||
|
std::shared_ptr<const Value> value,
|
|||
|
std::string arg_help
|
|||
|
)
|
|||
|
{
|
|||
|
auto stringDesc = toLocalString(std::move(desc));
|
|||
|
auto option = std::make_shared<OptionDetails>(stringDesc, value);
|
|||
|
|
|||
|
if (s.size() > 0)
|
|||
|
{
|
|||
|
add_one_option(s, option);
|
|||
|
}
|
|||
|
|
|||
|
if (l.size() > 0)
|
|||
|
{
|
|||
|
add_one_option(l, option);
|
|||
|
}
|
|||
|
|
|||
|
//add the help details
|
|||
|
auto& options = m_help[group];
|
|||
|
|
|||
|
options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
|
|||
|
value->has_arg(),
|
|||
|
value->has_default(), value->get_default_value(),
|
|||
|
value->has_implicit(), value->get_implicit_value(),
|
|||
|
std::move(arg_help)});
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
Options::add_one_option
|
|||
|
(
|
|||
|
const std::string& option,
|
|||
|
std::shared_ptr<OptionDetails> details
|
|||
|
)
|
|||
|
{
|
|||
|
auto in = m_options.emplace(option, details);
|
|||
|
|
|||
|
if (!in.second)
|
|||
|
{
|
|||
|
throw option_exists_error(option);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
String
|
|||
|
Options::help_one_group(const std::string& g) const
|
|||
|
{
|
|||
|
typedef std::vector<std::pair<String, String>> OptionHelp;
|
|||
|
|
|||
|
auto group = m_help.find(g);
|
|||
|
if (group == m_help.end())
|
|||
|
{
|
|||
|
return "";
|
|||
|
}
|
|||
|
|
|||
|
OptionHelp format;
|
|||
|
|
|||
|
size_t longest = 0;
|
|||
|
|
|||
|
String result;
|
|||
|
|
|||
|
if (!g.empty())
|
|||
|
{
|
|||
|
result += toLocalString(" " + g + " options:\n\n");
|
|||
|
}
|
|||
|
|
|||
|
for (const auto& o : group->second.options)
|
|||
|
{
|
|||
|
auto s = format_option(o);
|
|||
|
longest = std::max(longest, stringLength(s));
|
|||
|
format.push_back(std::make_pair(s, String()));
|
|||
|
}
|
|||
|
|
|||
|
longest = std::min(longest, static_cast<size_t>(OPTION_LONGEST));
|
|||
|
|
|||
|
//widest allowed description
|
|||
|
auto allowed = size_t{76} - longest - OPTION_DESC_GAP;
|
|||
|
|
|||
|
auto fiter = format.begin();
|
|||
|
for (const auto& o : group->second.options)
|
|||
|
{
|
|||
|
auto d = format_description(o, longest + OPTION_DESC_GAP, allowed);
|
|||
|
|
|||
|
result += fiter->first;
|
|||
|
if (stringLength(fiter->first) > longest)
|
|||
|
{
|
|||
|
result += "\n";
|
|||
|
result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
result += toLocalString(std::string(longest + OPTION_DESC_GAP -
|
|||
|
stringLength(fiter->first),
|
|||
|
' '));
|
|||
|
}
|
|||
|
result += d;
|
|||
|
result += "\n";
|
|||
|
|
|||
|
++fiter;
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
std::string
|
|||
|
Options::help(const std::vector<std::string>& groups) const
|
|||
|
{
|
|||
|
String result = "Usage:\n " + toLocalString(m_program) + " [OPTION...]"
|
|||
|
+ m_help_string + "\n\n";
|
|||
|
|
|||
|
for (std::size_t i = 0; i < groups.size(); ++i)
|
|||
|
{
|
|||
|
result += help_one_group(groups[i]);
|
|||
|
if (i < groups.size() - 1)
|
|||
|
{
|
|||
|
result += "\n";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return toUTF8String(result);
|
|||
|
}
|
|||
|
|
|||
|
const std::vector<std::string>
|
|||
|
Options::groups() const
|
|||
|
{
|
|||
|
std::vector<std::string> g;
|
|||
|
|
|||
|
std::transform(
|
|||
|
m_help.begin(),
|
|||
|
m_help.end(),
|
|||
|
std::back_inserter(g),
|
|||
|
[] (const std::map<std::string, HelpGroupDetails>::value_type& pair)
|
|||
|
{
|
|||
|
return pair.first;
|
|||
|
}
|
|||
|
);
|
|||
|
|
|||
|
return g;
|
|||
|
}
|
|||
|
|
|||
|
const HelpGroupDetails&
|
|||
|
Options::group_help(const std::string& group) const
|
|||
|
{
|
|||
|
return m_help.at(group);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
#if defined(__GNU__)
|
|||
|
#pragma GCC diagnostic pop
|
|||
|
#endif
|
|||
|
|
|||
|
#endif //CXX_OPTS_HPP
|