mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-05-01 13:13:53 +00:00
Separate files, plus way to combine
This commit is contained in:
parent
44a27be54f
commit
a12a94c4c1
@ -1,4 +1,4 @@
|
||||
#include "CLI.hpp"
|
||||
#include "CLI/CLI.hpp"
|
||||
|
||||
|
||||
int main (int argc, char** argv) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "CLI.hpp"
|
||||
#include "CLI/CLI.hpp"
|
||||
|
||||
|
||||
int main (int argc, char** argv) {
|
||||
|
@ -8,628 +8,26 @@
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
#include <set>
|
||||
#include <iomanip>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
#include <locale>
|
||||
|
||||
// C standard library
|
||||
// Only needed for existence checking
|
||||
// Could be swapped for filesystem in C++17
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
// CLI Library includes
|
||||
#include "CLI/Error.hpp"
|
||||
#include "CLI/TypeTools.hpp"
|
||||
#include "CLI/StringTools.hpp"
|
||||
#include "CLI/Split.hpp"
|
||||
#include "CLI/Combiner.hpp"
|
||||
#include "CLI/Option.hpp"
|
||||
#include "CLI/Value.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
|
||||
// Error definitions
|
||||
|
||||
struct Error : public std::runtime_error {
|
||||
int exit_code;
|
||||
bool print_help;
|
||||
Error(std::string parent, std::string name, int exit_code=255, bool print_help=true) : runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {}
|
||||
};
|
||||
|
||||
struct Success : public Error {
|
||||
Success() : Error("Success", "Successfully completed, should be caught and quit", 0, false) {}
|
||||
};
|
||||
|
||||
struct CallForHelp : public Error {
|
||||
CallForHelp() : Error("CallForHelp", "This should be caught in your main function, see examples", 0) {}
|
||||
};
|
||||
|
||||
struct BadNameString : public Error {
|
||||
BadNameString(std::string name) : Error("BadNameString", name, 1) {}
|
||||
};
|
||||
|
||||
|
||||
struct ParseError : public Error {
|
||||
ParseError(std::string name) : Error("ParseError", name, 2) {}
|
||||
};
|
||||
|
||||
struct OptionAlreadyAdded : public Error {
|
||||
OptionAlreadyAdded(std::string name) : Error("OptionAlreadyAdded", name, 3) {}
|
||||
};
|
||||
|
||||
struct OptionNotFound : public Error {
|
||||
OptionNotFound(std::string name) : Error("OptionNotFound", name, 4) {}
|
||||
};
|
||||
|
||||
struct RequiredError : public Error {
|
||||
RequiredError(std::string name) : Error("RequiredError", name, 5) {}
|
||||
};
|
||||
|
||||
struct PositionalError : public Error {
|
||||
PositionalError(std::string name) : Error("PositionalError", name, 6) {}
|
||||
};
|
||||
|
||||
struct HorribleError : public Error {
|
||||
HorribleError(std::string name) : Error("HorribleError", "(You should never see this error) " + name, 7) {}
|
||||
};
|
||||
struct IncorrectConstruction : public Error {
|
||||
IncorrectConstruction(std::string name) : Error("IncorrectConstruction", name, 8) {}
|
||||
};
|
||||
struct EmptyError : public Error {
|
||||
EmptyError(std::string name) : Error("EmptyError", name, 9) {}
|
||||
};
|
||||
|
||||
|
||||
// Type tools
|
||||
//
|
||||
// Copied from C++14
|
||||
#if __cplusplus < 201402L
|
||||
template< bool B, class T = void >
|
||||
using enable_if_t = typename std::enable_if<B,T>::type;
|
||||
#else
|
||||
using std::enable_if_t;
|
||||
#endif
|
||||
// If your compiler supports C++14, you can use that definition instead
|
||||
|
||||
template <typename T>
|
||||
struct is_vector {
|
||||
static const bool value = false;
|
||||
};
|
||||
|
||||
|
||||
template<class T, class A>
|
||||
struct is_vector<std::vector<T, A> > {
|
||||
static bool const value = true;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_bool {
|
||||
static const bool value = false;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct is_bool<bool> {
|
||||
static bool const value = true;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
// Based generally on https://rmf.io/cxx11/almost-static-if
|
||||
/// Simple empty scoped class
|
||||
enum class enabler {};
|
||||
|
||||
/// An instance to use in EnableIf
|
||||
constexpr enabler dummy = {};
|
||||
|
||||
/// Simple function to join a string
|
||||
template <typename T>
|
||||
std::string join(const T& v, std::string delim = ",") {
|
||||
std::ostringstream s;
|
||||
size_t start = 0;
|
||||
for (const auto& i : v) {
|
||||
if(start++ > 0)
|
||||
s << delim;
|
||||
s << i;
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
|
||||
/// Was going to be based on
|
||||
/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template
|
||||
/// But this is cleaner and works better in this case
|
||||
|
||||
template<typename T,
|
||||
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy>
|
||||
constexpr const char* type_name() {
|
||||
return "INT";
|
||||
}
|
||||
|
||||
template<typename T,
|
||||
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
|
||||
constexpr const char* type_name() {
|
||||
return "UINT";
|
||||
}
|
||||
|
||||
|
||||
template<typename T,
|
||||
enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
|
||||
constexpr const char* type_name() {
|
||||
return "FLOAT";
|
||||
}
|
||||
|
||||
|
||||
/// This one should not be used, since vector types print the internal type
|
||||
template<typename T,
|
||||
enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
|
||||
constexpr const char* type_name() {
|
||||
return "VECTOR";
|
||||
}
|
||||
|
||||
|
||||
template<typename T,
|
||||
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value
|
||||
, detail::enabler> = detail::dummy>
|
||||
constexpr const char* type_name() {
|
||||
return "STRING";
|
||||
}
|
||||
|
||||
void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) {
|
||||
name = " " + name;
|
||||
out << std::setw(wid) << std::left << name;
|
||||
if(description != "") {
|
||||
if(name.length()>=wid)
|
||||
out << std::endl << std::setw(wid) << "";
|
||||
out << description << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
struct Combiner {
|
||||
int num;
|
||||
bool required;
|
||||
bool defaulted;
|
||||
std::vector<std::function<bool(std::string)>> validators;
|
||||
|
||||
/// Can be or-ed together
|
||||
Combiner operator | (Combiner b) const {
|
||||
Combiner self;
|
||||
self.num = std::min(num, b.num) == -1 ? -1 : std::max(num, b.num);
|
||||
self.required = required || b.required;
|
||||
self.defaulted = defaulted || b.defaulted;
|
||||
self.validators.reserve(validators.size() + b.validators.size());
|
||||
self.validators.insert(self.validators.end(), validators.begin(), validators.end());
|
||||
self.validators.insert(self.validators.end(), b.validators.begin(), b.validators.end());
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Call to give the number of arguments expected on cli
|
||||
Combiner operator() (int n) const {
|
||||
Combiner self = *this;
|
||||
self.num = n;
|
||||
return self;
|
||||
}
|
||||
/// Call to give a validator
|
||||
Combiner operator() (std::function<bool(std::string)> func) const {
|
||||
Combiner self = *this;
|
||||
self.validators.push_back(func);
|
||||
return self;
|
||||
}
|
||||
};
|
||||
|
||||
bool _ExistingFile(std::string filename) {
|
||||
// std::fstream f(name.c_str());
|
||||
// return f.good();
|
||||
// Fastest way according to http://stackoverflow.com/questions/12774207/fastest-way-to-check-if-a-file-exist-using-standard-c-c11-c
|
||||
struct stat buffer;
|
||||
return (stat(filename.c_str(), &buffer) == 0);
|
||||
}
|
||||
|
||||
bool _ExistingDirectory(std::string filename) {
|
||||
struct stat buffer;
|
||||
if(stat(filename.c_str(), &buffer) == 0 && (buffer.st_mode & S_IFDIR) )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _NonexistentPath(std::string filename) {
|
||||
struct stat buffer;
|
||||
return stat(filename.c_str(), &buffer) != 0;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
bool valid_first_char(T c) {
|
||||
return std::isalpha(c) || c=='_';
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool valid_later_char(T c) {
|
||||
return std::isalnum(c) || c=='_' || c=='.' || c=='-';
|
||||
}
|
||||
|
||||
inline bool valid_name_string(const std::string &str) {
|
||||
if(str.size()<1 || !valid_first_char(str[0]))
|
||||
return false;
|
||||
for(auto c : str.substr(1))
|
||||
if(!valid_later_char(c))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns false if not a short option. Otherwise, sets opt name and rest and returns true
|
||||
inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) {
|
||||
if(current.size()>1 && current[0] == '-' && valid_first_char(current[1])) {
|
||||
name = current.substr(1,1);
|
||||
rest = current.substr(2);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true
|
||||
inline bool split_long(const std::string ¤t, std::string &name, std::string &value) {
|
||||
if(current.size()>2 && current.substr(0,2) == "--" && valid_first_char(current[2])) {
|
||||
auto loc = current.find("=");
|
||||
if(loc != std::string::npos) {
|
||||
name = current.substr(2,loc-2);
|
||||
value = current.substr(loc+1);
|
||||
} else {
|
||||
name = current.substr(2);
|
||||
value = "";
|
||||
}
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Splits a string into multiple long and short names
|
||||
inline std::vector<std::string> split_names(std::string current) {
|
||||
std::vector<std::string> output;
|
||||
size_t val;
|
||||
while((val = current.find(",")) != std::string::npos) {
|
||||
output.push_back(current.substr(0,val));
|
||||
current = current.substr(val+1);
|
||||
}
|
||||
output.push_back(current);
|
||||
return output;
|
||||
|
||||
}
|
||||
|
||||
|
||||
inline std::tuple<std::vector<std::string>,std::vector<std::string>, std::string>
|
||||
get_names(const std::vector<std::string> &input) {
|
||||
|
||||
std::vector<std::string> short_names;
|
||||
std::vector<std::string> long_names;
|
||||
std::string pos_name;
|
||||
|
||||
for(std::string name : input) {
|
||||
if(name.length() == 0)
|
||||
continue;
|
||||
else if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
|
||||
if(name.length()==2 && valid_first_char(name[1]))
|
||||
short_names.push_back(std::string(1,name[1]));
|
||||
else
|
||||
throw BadNameString("Invalid one char name: "+name);
|
||||
} else if(name.length() > 2 && name.substr(0,2) == "--") {
|
||||
name = name.substr(2);
|
||||
if(valid_name_string(name))
|
||||
long_names.push_back(name);
|
||||
else
|
||||
throw BadNameString("Bad long name: "+name);
|
||||
} else if(name == "-" || name == "--") {
|
||||
throw BadNameString("Must have a name, not just dashes");
|
||||
} else {
|
||||
if(pos_name.length() > 0)
|
||||
throw BadNameString("Only one positional name allowed, remove: "+name);
|
||||
pos_name = name;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return std::tuple<std::vector<std::string>,std::vector<std::string>, std::string>
|
||||
(short_names, long_names, pos_name);
|
||||
}
|
||||
|
||||
// Integers
|
||||
template<typename T, enable_if_t<std::is_integral<T>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T& output) {
|
||||
try{
|
||||
output = (T) std::stoll(input);
|
||||
return true;
|
||||
} catch (std::invalid_argument) {
|
||||
return false;
|
||||
} catch (std::out_of_range) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Floats
|
||||
template<typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T& output) {
|
||||
try{
|
||||
output = (T) std::stold(input);
|
||||
return true;
|
||||
} catch (std::invalid_argument) {
|
||||
return false;
|
||||
} catch (std::out_of_range) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Vector
|
||||
template<typename T,
|
||||
enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T& output) {
|
||||
if(output.size() == input.size())
|
||||
output.resize(input.size());
|
||||
for(size_t i=0; i<input.size(); i++)
|
||||
output[i] = input[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
// String and similar
|
||||
template<typename T,
|
||||
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value
|
||||
, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T& output) {
|
||||
output = input;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Defines for common Combiners (don't use combiners directly)
|
||||
|
||||
const detail::Combiner Nothing {0, false, false, {}};
|
||||
const detail::Combiner Required {1, true, false, {}};
|
||||
const detail::Combiner Default {1, false, true, {}};
|
||||
const detail::Combiner Args {-1, false, false, {}};
|
||||
const detail::Combiner Validators {1, false, false, {}};
|
||||
|
||||
// Warning about using these validators:
|
||||
// The files could be added/deleted after the validation. This is not common,
|
||||
// but if this is a possibility, check the file you open afterwards
|
||||
const detail::Combiner ExistingFile {1, false, false, {detail::_ExistingFile}};
|
||||
const detail::Combiner ExistingDirectory {1, false, false, {detail::_ExistingDirectory}};
|
||||
const detail::Combiner NonexistentPath {1, false, false, {detail::_NonexistentPath}};
|
||||
|
||||
typedef std::vector<std::vector<std::string>> results_t;
|
||||
typedef std::function<bool(results_t)> callback_t;
|
||||
|
||||
|
||||
class App;
|
||||
|
||||
class Option {
|
||||
friend App;
|
||||
protected:
|
||||
// Config
|
||||
std::vector<std::string> snames;
|
||||
std::vector<std::string> lnames;
|
||||
std::string pname;
|
||||
|
||||
detail::Combiner opts;
|
||||
std::string description;
|
||||
callback_t callback;
|
||||
|
||||
// These are for help strings
|
||||
std::string defaultval;
|
||||
std::string typeval;
|
||||
|
||||
// Results
|
||||
results_t results {};
|
||||
|
||||
|
||||
public:
|
||||
Option(std::string name, std::string description = "", detail::Combiner opts=Nothing, std::function<bool(results_t)> callback=[](results_t){return true;}) :
|
||||
opts(opts), description(description), callback(callback){
|
||||
std::tie(snames, lnames, pname) = detail::get_names(detail::split_names(name));
|
||||
}
|
||||
|
||||
/// Clear the parsed results (mostly for testing)
|
||||
void clear() {
|
||||
results.clear();
|
||||
}
|
||||
|
||||
/// True if option is required
|
||||
bool required() const {
|
||||
return opts.required;
|
||||
}
|
||||
|
||||
/// The number of arguments the option expects
|
||||
int expected() const {
|
||||
return opts.num;
|
||||
}
|
||||
|
||||
/// True if the argument can be given directly
|
||||
bool positional() const {
|
||||
return pname.length() > 0;
|
||||
}
|
||||
|
||||
/// True if option has at least one non-positional name
|
||||
bool nonpositional() const {
|
||||
return (snames.size() + lnames.size()) > 0;
|
||||
}
|
||||
|
||||
/// True if this should print the default string
|
||||
bool defaulted() const {
|
||||
return opts.defaulted;
|
||||
}
|
||||
|
||||
/// True if option has description
|
||||
bool has_description() const {
|
||||
return description.length() > 0;
|
||||
}
|
||||
|
||||
/// Get the description
|
||||
const std::string& get_description() const {
|
||||
return description;
|
||||
}
|
||||
|
||||
/// The name and any extras needed for positionals
|
||||
std::string help_positional() const {
|
||||
std::string out = pname;
|
||||
if(expected()<1)
|
||||
out = out + "x" + std::to_string(expected());
|
||||
else if(expected()==-1)
|
||||
out = out + "...";
|
||||
out = required() ? out : "["+out+"]";
|
||||
return out;
|
||||
}
|
||||
|
||||
// Just the pname
|
||||
std::string get_pname() const {
|
||||
return pname;
|
||||
}
|
||||
|
||||
/// Process the callback
|
||||
bool run_callback() const {
|
||||
if(opts.validators.size()>0) {
|
||||
for(const std::string & result : flatten_results())
|
||||
for(const std::function<bool(std::string)> &vali : opts.validators)
|
||||
if(!vali(result))
|
||||
return false;
|
||||
}
|
||||
return callback(results);
|
||||
}
|
||||
|
||||
/// If options share any of the same names, they are equal (not counting positional)
|
||||
bool operator== (const Option& other) const {
|
||||
for(const std::string &sname : snames)
|
||||
for(const std::string &othersname : other.snames)
|
||||
if(sname == othersname)
|
||||
return true;
|
||||
for(const std::string &lname : lnames)
|
||||
for(const std::string &otherlname : other.lnames)
|
||||
if(lname == otherlname)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Gets a , sep list of names. Does not include the positional name.
|
||||
std::string get_name() const {
|
||||
std::vector<std::string> name_list;
|
||||
for(const std::string& sname : snames)
|
||||
name_list.push_back("-"+sname);
|
||||
for(const std::string& lname : lnames)
|
||||
name_list.push_back("--"+lname);
|
||||
return detail::join(name_list);
|
||||
}
|
||||
|
||||
/// Check a name. Requires "-" or "--" for short / long, supports positional name
|
||||
bool check_name(std::string name) const {
|
||||
|
||||
if(name.length()>2 && name.substr(0,2) == "--")
|
||||
return check_lname(name.substr(2));
|
||||
else if (name.length()>1 && name.substr(0,1) == "-")
|
||||
return check_sname(name.substr(1));
|
||||
else
|
||||
return name == pname;
|
||||
}
|
||||
|
||||
/// Requires "-" to be removed from string
|
||||
bool check_sname(const std::string& name) const {
|
||||
return std::find(std::begin(snames), std::end(snames), name) != std::end(snames);
|
||||
}
|
||||
|
||||
/// Requires "--" to be removed from string
|
||||
bool check_lname(const std::string& name) const {
|
||||
return std::find(std::begin(lnames), std::end(lnames), name) != std::end(lnames);
|
||||
}
|
||||
|
||||
|
||||
/// Puts a result at position r
|
||||
void add_result(int r, std::string s) {
|
||||
results.at(r).push_back(s);
|
||||
}
|
||||
|
||||
/// Starts a new results vector (used for r in add_result)
|
||||
int get_new() {
|
||||
results.emplace_back();
|
||||
return results.size() - 1;
|
||||
}
|
||||
|
||||
/// Count the total number of times an option was passed
|
||||
int count() const {
|
||||
int out = 0;
|
||||
for(const std::vector<std::string>& v : results)
|
||||
out += v.size();
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Diagnostic representation
|
||||
std::string string() const {
|
||||
std::string val = "Option: " + get_name() + "\n"
|
||||
+ " " + description + "\n"
|
||||
+ " [";
|
||||
for(const auto& item : results) {
|
||||
if(&item!=&results[0])
|
||||
val+="],[";
|
||||
val += detail::join(item);
|
||||
}
|
||||
val += "]";
|
||||
return val;
|
||||
}
|
||||
|
||||
/// The first half of the help print, name plus default, etc
|
||||
std::string help_name() const {
|
||||
std::stringstream out;
|
||||
out << get_name();
|
||||
if(expected() != 0) {
|
||||
if(typeval != "")
|
||||
out << " " << typeval;
|
||||
if(defaultval != "")
|
||||
out << "=" << defaultval;
|
||||
if(expected() > 1)
|
||||
out << " x " << expected();
|
||||
if(expected() == -1)
|
||||
out << " ...";
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
/// Produce a flattened vector of results, vs. a vector of vectors.
|
||||
std::vector<std::string> flatten_results() const {
|
||||
std::vector<std::string> output;
|
||||
for(const std::vector<std::string> result : results)
|
||||
output.insert(std::end(output), std::begin(result), std::end(result));
|
||||
return output;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
enum class Classifer {NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND};
|
||||
|
||||
|
||||
// Prototype return value test
|
||||
template <typename T>
|
||||
class Value {
|
||||
friend App;
|
||||
protected:
|
||||
std::shared_ptr<std::unique_ptr<T>> value {new std::unique_ptr<T>()};
|
||||
std::string name;
|
||||
public:
|
||||
Value(std::string name) : name(name) {}
|
||||
|
||||
operator bool() const {return (bool) *value;}
|
||||
|
||||
T& get() const {
|
||||
if(*value)
|
||||
return **value;
|
||||
else
|
||||
throw EmptyError(name);
|
||||
}
|
||||
/// Note this does not throw on assignment, though
|
||||
/// afterwards it seems to work fine. Best to use
|
||||
/// explicit * notation.
|
||||
T& operator *() const {
|
||||
return get();
|
||||
}
|
||||
};
|
||||
|
||||
/// Creates a command line program, with very few defaults.
|
||||
/** To use, create a new Program() instance with argc, argv, and a help description. The templated
|
||||
* add_option methods make it easy to prepare options. Remember to call `.start` before starting your
|
||||
@ -1391,4 +789,6 @@ public:
|
||||
return name;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
15
include/CLI/CLI.hpp
Normal file
15
include/CLI/CLI.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
// Distributed under the LGPL version 3.0 license. See accompanying
|
||||
// file LICENSE or https://github.com/henryiii/CLI11 for details.
|
||||
|
||||
// CLI Library includes
|
||||
#include "CLI/Error.hpp"
|
||||
#include "CLI/TypeTools.hpp"
|
||||
#include "CLI/StringTools.hpp"
|
||||
#include "CLI/Split.hpp"
|
||||
#include "CLI/Combiner.hpp"
|
||||
#include "CLI/Option.hpp"
|
||||
#include "CLI/Value.hpp"
|
||||
#include "CLI/App.hpp"
|
||||
|
99
include/CLI/Combiner.hpp
Normal file
99
include/CLI/Combiner.hpp
Normal file
@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
// Distributed under the LGPL version 3.0 license. See accompanying
|
||||
// file LICENSE or https://github.com/henryiii/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
|
||||
// C standard library
|
||||
// Only needed for existence checking
|
||||
// Could be swapped for filesystem in C++17
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace CLI {
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct Combiner {
|
||||
int num;
|
||||
bool required;
|
||||
bool defaulted;
|
||||
std::vector<std::function<bool(std::string)>> validators;
|
||||
|
||||
/// Can be or-ed together
|
||||
Combiner operator | (Combiner b) const {
|
||||
Combiner self;
|
||||
self.num = std::min(num, b.num) == -1 ? -1 : std::max(num, b.num);
|
||||
self.required = required || b.required;
|
||||
self.defaulted = defaulted || b.defaulted;
|
||||
self.validators.reserve(validators.size() + b.validators.size());
|
||||
self.validators.insert(self.validators.end(), validators.begin(), validators.end());
|
||||
self.validators.insert(self.validators.end(), b.validators.begin(), b.validators.end());
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Call to give the number of arguments expected on cli
|
||||
Combiner operator() (int n) const {
|
||||
Combiner self = *this;
|
||||
self.num = n;
|
||||
return self;
|
||||
}
|
||||
/// Call to give a validator
|
||||
Combiner operator() (std::function<bool(std::string)> func) const {
|
||||
Combiner self = *this;
|
||||
self.validators.push_back(func);
|
||||
return self;
|
||||
}
|
||||
};
|
||||
|
||||
/// Check for an existing file
|
||||
bool _ExistingFile(std::string filename) {
|
||||
// std::fstream f(name.c_str());
|
||||
// return f.good();
|
||||
// Fastest way according to http://stackoverflow.com/questions/12774207/fastest-way-to-check-if-a-file-exist-using-standard-c-c11-c
|
||||
struct stat buffer;
|
||||
return (stat(filename.c_str(), &buffer) == 0);
|
||||
}
|
||||
|
||||
/// Check for an existing directory
|
||||
bool _ExistingDirectory(std::string filename) {
|
||||
struct stat buffer;
|
||||
if(stat(filename.c_str(), &buffer) == 0 && (buffer.st_mode & S_IFDIR) )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Check for a non-existing path
|
||||
bool _NonexistentPath(std::string filename) {
|
||||
struct stat buffer;
|
||||
return stat(filename.c_str(), &buffer) != 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Defines for common Combiners (don't use combiners directly)
|
||||
|
||||
const detail::Combiner Nothing {0, false, false, {}};
|
||||
const detail::Combiner Required {1, true, false, {}};
|
||||
const detail::Combiner Default {1, false, true, {}};
|
||||
const detail::Combiner Args {-1, false, false, {}};
|
||||
const detail::Combiner Validators {1, false, false, {}};
|
||||
|
||||
// Warning about using these validators:
|
||||
// The files could be added/deleted after the validation. This is not common,
|
||||
// but if this is a possibility, check the file you open afterwards
|
||||
const detail::Combiner ExistingFile {1, false, false, {detail::_ExistingFile}};
|
||||
const detail::Combiner ExistingDirectory {1, false, false, {detail::_ExistingDirectory}};
|
||||
const detail::Combiner NonexistentPath {1, false, false, {detail::_NonexistentPath}};
|
||||
|
||||
|
||||
}
|
64
include/CLI/Error.hpp
Normal file
64
include/CLI/Error.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
// Distributed under the LGPL version 3.0 license. See accompanying
|
||||
// file LICENSE or https://github.com/henryiii/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace CLI {
|
||||
|
||||
// Error definitions
|
||||
|
||||
|
||||
struct Error : public std::runtime_error {
|
||||
int exit_code;
|
||||
bool print_help;
|
||||
Error(std::string parent, std::string name, int exit_code=255, bool print_help=true) : runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {}
|
||||
};
|
||||
|
||||
struct Success : public Error {
|
||||
Success() : Error("Success", "Successfully completed, should be caught and quit", 0, false) {}
|
||||
};
|
||||
|
||||
struct CallForHelp : public Error {
|
||||
CallForHelp() : Error("CallForHelp", "This should be caught in your main function, see examples", 0) {}
|
||||
};
|
||||
|
||||
struct BadNameString : public Error {
|
||||
BadNameString(std::string name) : Error("BadNameString", name, 1) {}
|
||||
};
|
||||
|
||||
|
||||
struct ParseError : public Error {
|
||||
ParseError(std::string name) : Error("ParseError", name, 2) {}
|
||||
};
|
||||
|
||||
struct OptionAlreadyAdded : public Error {
|
||||
OptionAlreadyAdded(std::string name) : Error("OptionAlreadyAdded", name, 3) {}
|
||||
};
|
||||
|
||||
struct OptionNotFound : public Error {
|
||||
OptionNotFound(std::string name) : Error("OptionNotFound", name, 4) {}
|
||||
};
|
||||
|
||||
struct RequiredError : public Error {
|
||||
RequiredError(std::string name) : Error("RequiredError", name, 5) {}
|
||||
};
|
||||
|
||||
struct PositionalError : public Error {
|
||||
PositionalError(std::string name) : Error("PositionalError", name, 6) {}
|
||||
};
|
||||
|
||||
struct HorribleError : public Error {
|
||||
HorribleError(std::string name) : Error("HorribleError", "(You should never see this error) " + name, 7) {}
|
||||
};
|
||||
struct IncorrectConstruction : public Error {
|
||||
IncorrectConstruction(std::string name) : Error("IncorrectConstruction", name, 8) {}
|
||||
};
|
||||
struct EmptyError : public Error {
|
||||
EmptyError(std::string name) : Error("EmptyError", name, 9) {}
|
||||
};
|
||||
|
||||
}
|
224
include/CLI/Option.hpp
Normal file
224
include/CLI/Option.hpp
Normal file
@ -0,0 +1,224 @@
|
||||
#pragma once
|
||||
|
||||
// Distributed under the LGPL version 3.0 license. See accompanying
|
||||
// file LICENSE or https://github.com/henryiii/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <algorithm>
|
||||
|
||||
#include "CLI/StringTools.hpp"
|
||||
#include "CLI/Split.hpp"
|
||||
#include "CLI/Combiner.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
typedef std::vector<std::vector<std::string>> results_t;
|
||||
typedef std::function<bool(results_t)> callback_t;
|
||||
|
||||
|
||||
class App;
|
||||
|
||||
class Option {
|
||||
friend App;
|
||||
protected:
|
||||
// Config
|
||||
std::vector<std::string> snames;
|
||||
std::vector<std::string> lnames;
|
||||
std::string pname;
|
||||
|
||||
detail::Combiner opts;
|
||||
std::string description;
|
||||
callback_t callback;
|
||||
|
||||
// These are for help strings
|
||||
std::string defaultval;
|
||||
std::string typeval;
|
||||
|
||||
// Results
|
||||
results_t results {};
|
||||
|
||||
|
||||
public:
|
||||
Option(std::string name, std::string description = "", detail::Combiner opts=Nothing, std::function<bool(results_t)> callback=[](results_t){return true;}) :
|
||||
opts(opts), description(description), callback(callback){
|
||||
std::tie(snames, lnames, pname) = detail::get_names(detail::split_names(name));
|
||||
}
|
||||
|
||||
/// Clear the parsed results (mostly for testing)
|
||||
void clear() {
|
||||
results.clear();
|
||||
}
|
||||
|
||||
/// True if option is required
|
||||
bool required() const {
|
||||
return opts.required;
|
||||
}
|
||||
|
||||
/// The number of arguments the option expects
|
||||
int expected() const {
|
||||
return opts.num;
|
||||
}
|
||||
|
||||
/// True if the argument can be given directly
|
||||
bool positional() const {
|
||||
return pname.length() > 0;
|
||||
}
|
||||
|
||||
/// True if option has at least one non-positional name
|
||||
bool nonpositional() const {
|
||||
return (snames.size() + lnames.size()) > 0;
|
||||
}
|
||||
|
||||
/// True if this should print the default string
|
||||
bool defaulted() const {
|
||||
return opts.defaulted;
|
||||
}
|
||||
|
||||
/// True if option has description
|
||||
bool has_description() const {
|
||||
return description.length() > 0;
|
||||
}
|
||||
|
||||
/// Get the description
|
||||
const std::string& get_description() const {
|
||||
return description;
|
||||
}
|
||||
|
||||
/// The name and any extras needed for positionals
|
||||
std::string help_positional() const {
|
||||
std::string out = pname;
|
||||
if(expected()<1)
|
||||
out = out + "x" + std::to_string(expected());
|
||||
else if(expected()==-1)
|
||||
out = out + "...";
|
||||
out = required() ? out : "["+out+"]";
|
||||
return out;
|
||||
}
|
||||
|
||||
// Just the pname
|
||||
std::string get_pname() const {
|
||||
return pname;
|
||||
}
|
||||
|
||||
/// Process the callback
|
||||
bool run_callback() const {
|
||||
if(opts.validators.size()>0) {
|
||||
for(const std::string & result : flatten_results())
|
||||
for(const std::function<bool(std::string)> &vali : opts.validators)
|
||||
if(!vali(result))
|
||||
return false;
|
||||
}
|
||||
return callback(results);
|
||||
}
|
||||
|
||||
/// If options share any of the same names, they are equal (not counting positional)
|
||||
bool operator== (const Option& other) const {
|
||||
for(const std::string &sname : snames)
|
||||
for(const std::string &othersname : other.snames)
|
||||
if(sname == othersname)
|
||||
return true;
|
||||
for(const std::string &lname : lnames)
|
||||
for(const std::string &otherlname : other.lnames)
|
||||
if(lname == otherlname)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Gets a , sep list of names. Does not include the positional name.
|
||||
std::string get_name() const {
|
||||
std::vector<std::string> name_list;
|
||||
for(const std::string& sname : snames)
|
||||
name_list.push_back("-"+sname);
|
||||
for(const std::string& lname : lnames)
|
||||
name_list.push_back("--"+lname);
|
||||
return detail::join(name_list);
|
||||
}
|
||||
|
||||
/// Check a name. Requires "-" or "--" for short / long, supports positional name
|
||||
bool check_name(std::string name) const {
|
||||
|
||||
if(name.length()>2 && name.substr(0,2) == "--")
|
||||
return check_lname(name.substr(2));
|
||||
else if (name.length()>1 && name.substr(0,1) == "-")
|
||||
return check_sname(name.substr(1));
|
||||
else
|
||||
return name == pname;
|
||||
}
|
||||
|
||||
/// Requires "-" to be removed from string
|
||||
bool check_sname(const std::string& name) const {
|
||||
return std::find(std::begin(snames), std::end(snames), name) != std::end(snames);
|
||||
}
|
||||
|
||||
/// Requires "--" to be removed from string
|
||||
bool check_lname(const std::string& name) const {
|
||||
return std::find(std::begin(lnames), std::end(lnames), name) != std::end(lnames);
|
||||
}
|
||||
|
||||
|
||||
/// Puts a result at position r
|
||||
void add_result(int r, std::string s) {
|
||||
results.at(r).push_back(s);
|
||||
}
|
||||
|
||||
/// Starts a new results vector (used for r in add_result)
|
||||
int get_new() {
|
||||
results.emplace_back();
|
||||
return results.size() - 1;
|
||||
}
|
||||
|
||||
/// Count the total number of times an option was passed
|
||||
int count() const {
|
||||
int out = 0;
|
||||
for(const std::vector<std::string>& v : results)
|
||||
out += v.size();
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Diagnostic representation
|
||||
std::string string() const {
|
||||
std::string val = "Option: " + get_name() + "\n"
|
||||
+ " " + description + "\n"
|
||||
+ " [";
|
||||
for(const auto& item : results) {
|
||||
if(&item!=&results[0])
|
||||
val+="],[";
|
||||
val += detail::join(item);
|
||||
}
|
||||
val += "]";
|
||||
return val;
|
||||
}
|
||||
|
||||
/// The first half of the help print, name plus default, etc
|
||||
std::string help_name() const {
|
||||
std::stringstream out;
|
||||
out << get_name();
|
||||
if(expected() != 0) {
|
||||
if(typeval != "")
|
||||
out << " " << typeval;
|
||||
if(defaultval != "")
|
||||
out << "=" << defaultval;
|
||||
if(expected() > 1)
|
||||
out << " x " << expected();
|
||||
if(expected() == -1)
|
||||
out << " ...";
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
/// Produce a flattened vector of results, vs. a vector of vectors.
|
||||
std::vector<std::string> flatten_results() const {
|
||||
std::vector<std::string> output;
|
||||
for(const std::vector<std::string> result : results)
|
||||
output.insert(std::end(output), std::begin(result), std::end(result));
|
||||
return output;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
93
include/CLI/Split.hpp
Normal file
93
include/CLI/Split.hpp
Normal file
@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
// Distributed under the LGPL version 3.0 license. See accompanying
|
||||
// file LICENSE or https://github.com/henryiii/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include "CLI/Error.hpp"
|
||||
#include "CLI/StringTools.hpp"
|
||||
|
||||
namespace CLI {
|
||||
namespace detail {
|
||||
|
||||
// Returns false if not a short option. Otherwise, sets opt name and rest and returns true
|
||||
inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) {
|
||||
if(current.size()>1 && current[0] == '-' && valid_first_char(current[1])) {
|
||||
name = current.substr(1,1);
|
||||
rest = current.substr(2);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true
|
||||
inline bool split_long(const std::string ¤t, std::string &name, std::string &value) {
|
||||
if(current.size()>2 && current.substr(0,2) == "--" && valid_first_char(current[2])) {
|
||||
auto loc = current.find("=");
|
||||
if(loc != std::string::npos) {
|
||||
name = current.substr(2,loc-2);
|
||||
value = current.substr(loc+1);
|
||||
} else {
|
||||
name = current.substr(2);
|
||||
value = "";
|
||||
}
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Splits a string into multiple long and short names
|
||||
inline std::vector<std::string> split_names(std::string current) {
|
||||
std::vector<std::string> output;
|
||||
size_t val;
|
||||
while((val = current.find(",")) != std::string::npos) {
|
||||
output.push_back(current.substr(0,val));
|
||||
current = current.substr(val+1);
|
||||
}
|
||||
output.push_back(current);
|
||||
return output;
|
||||
|
||||
}
|
||||
|
||||
/// Get a vector of short names, one of long names, and a single name
|
||||
inline std::tuple<std::vector<std::string>,std::vector<std::string>, std::string>
|
||||
get_names(const std::vector<std::string> &input) {
|
||||
|
||||
std::vector<std::string> short_names;
|
||||
std::vector<std::string> long_names;
|
||||
std::string pos_name;
|
||||
|
||||
for(std::string name : input) {
|
||||
if(name.length() == 0)
|
||||
continue;
|
||||
else if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
|
||||
if(name.length()==2 && valid_first_char(name[1]))
|
||||
short_names.push_back(std::string(1,name[1]));
|
||||
else
|
||||
throw BadNameString("Invalid one char name: "+name);
|
||||
} else if(name.length() > 2 && name.substr(0,2) == "--") {
|
||||
name = name.substr(2);
|
||||
if(valid_name_string(name))
|
||||
long_names.push_back(name);
|
||||
else
|
||||
throw BadNameString("Bad long name: "+name);
|
||||
} else if(name == "-" || name == "--") {
|
||||
throw BadNameString("Must have a name, not just dashes");
|
||||
} else {
|
||||
if(pos_name.length() > 0)
|
||||
throw BadNameString("Only one positional name allowed, remove: "+name);
|
||||
pos_name = name;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return std::tuple<std::vector<std::string>,std::vector<std::string>, std::string>
|
||||
(short_names, long_names, pos_name);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
65
include/CLI/StringTools.hpp
Normal file
65
include/CLI/StringTools.hpp
Normal file
@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
// Distributed under the LGPL version 3.0 license. See accompanying
|
||||
// file LICENSE or https://github.com/henryiii/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <locale>
|
||||
#include <type_traits>
|
||||
|
||||
namespace CLI {
|
||||
namespace detail {
|
||||
|
||||
|
||||
/// Simple function to join a string
|
||||
template <typename T>
|
||||
std::string join(const T& v, std::string delim = ",") {
|
||||
std::ostringstream s;
|
||||
size_t start = 0;
|
||||
for (const auto& i : v) {
|
||||
if(start++ > 0)
|
||||
s << delim;
|
||||
s << i;
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
|
||||
/// Print a two part "help" string
|
||||
void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) {
|
||||
name = " " + name;
|
||||
out << std::setw(wid) << std::left << name;
|
||||
if(description != "") {
|
||||
if(name.length()>=wid)
|
||||
out << std::endl << std::setw(wid) << "";
|
||||
out << description << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify the first character of an option
|
||||
template<typename T>
|
||||
bool valid_first_char(T c) {
|
||||
return std::isalpha(c) || c=='_';
|
||||
}
|
||||
|
||||
/// Verify following characters of an option
|
||||
template<typename T>
|
||||
bool valid_later_char(T c) {
|
||||
return std::isalnum(c) || c=='_' || c=='.' || c=='-';
|
||||
}
|
||||
|
||||
/// Verify an option name
|
||||
inline bool valid_name_string(const std::string &str) {
|
||||
if(str.size()<1 || !valid_first_char(str[0]))
|
||||
return false;
|
||||
for(auto c : str.substr(1))
|
||||
if(!valid_later_char(c))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
149
include/CLI/TypeTools.hpp
Normal file
149
include/CLI/TypeTools.hpp
Normal file
@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
|
||||
// Distributed under the LGPL version 3.0 license. See accompanying
|
||||
// file LICENSE or https://github.com/henryiii/CLI11 for details.
|
||||
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
#include <exception>
|
||||
|
||||
namespace CLI {
|
||||
|
||||
// Type tools
|
||||
|
||||
// Copied from C++14
|
||||
#if __cplusplus < 201402L
|
||||
template< bool B, class T = void >
|
||||
using enable_if_t = typename std::enable_if<B,T>::type;
|
||||
#else
|
||||
// If your compiler supports C++14, you can use that definition instead
|
||||
using std::enable_if_t;
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
struct is_vector {
|
||||
static const bool value = false;
|
||||
};
|
||||
|
||||
|
||||
template<class T, class A>
|
||||
struct is_vector<std::vector<T, A> > {
|
||||
static bool const value = true;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_bool {
|
||||
static const bool value = false;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct is_bool<bool> {
|
||||
static bool const value = true;
|
||||
};
|
||||
|
||||
|
||||
namespace detail {
|
||||
// Based generally on https://rmf.io/cxx11/almost-static-if
|
||||
/// Simple empty scoped class
|
||||
enum class enabler {};
|
||||
|
||||
/// An instance to use in EnableIf
|
||||
constexpr enabler dummy = {};
|
||||
|
||||
|
||||
// Type name print
|
||||
|
||||
/// Was going to be based on
|
||||
/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template
|
||||
/// But this is cleaner and works better in this case
|
||||
|
||||
template<typename T,
|
||||
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy>
|
||||
constexpr const char* type_name() {
|
||||
return "INT";
|
||||
}
|
||||
|
||||
template<typename T,
|
||||
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
|
||||
constexpr const char* type_name() {
|
||||
return "UINT";
|
||||
}
|
||||
|
||||
|
||||
template<typename T,
|
||||
enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
|
||||
constexpr const char* type_name() {
|
||||
return "FLOAT";
|
||||
}
|
||||
|
||||
|
||||
/// This one should not be used, since vector types print the internal type
|
||||
template<typename T,
|
||||
enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
|
||||
constexpr const char* type_name() {
|
||||
return "VECTOR";
|
||||
}
|
||||
|
||||
|
||||
template<typename T,
|
||||
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value
|
||||
, detail::enabler> = detail::dummy>
|
||||
constexpr const char* type_name() {
|
||||
return "STRING";
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Lexical cast
|
||||
|
||||
|
||||
/// Integers
|
||||
template<typename T, enable_if_t<std::is_integral<T>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T& output) {
|
||||
try{
|
||||
output = (T) std::stoll(input);
|
||||
return true;
|
||||
} catch (std::invalid_argument) {
|
||||
return false;
|
||||
} catch (std::out_of_range) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Floats
|
||||
template<typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T& output) {
|
||||
try{
|
||||
output = (T) std::stold(input);
|
||||
return true;
|
||||
} catch (std::invalid_argument) {
|
||||
return false;
|
||||
} catch (std::out_of_range) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Vector
|
||||
template<typename T,
|
||||
enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T& output) {
|
||||
if(output.size() == input.size())
|
||||
output.resize(input.size());
|
||||
for(size_t i=0; i<input.size(); i++)
|
||||
output[i] = input[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
/// String and similar
|
||||
template<typename T,
|
||||
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value
|
||||
, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T& output) {
|
||||
output = input;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
41
include/CLI/Value.hpp
Normal file
41
include/CLI/Value.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
// Distributed under the LGPL version 3.0 license. See accompanying
|
||||
// file LICENSE or https://github.com/henryiii/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "CLI/Error.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
class App;
|
||||
|
||||
// Prototype return value test
|
||||
template <typename T>
|
||||
class Value {
|
||||
friend App;
|
||||
protected:
|
||||
std::shared_ptr<std::unique_ptr<T>> value {new std::unique_ptr<T>()};
|
||||
std::string name;
|
||||
public:
|
||||
Value(std::string name) : name(name) {}
|
||||
|
||||
operator bool() const {return (bool) *value;}
|
||||
|
||||
T& get() const {
|
||||
if(*value)
|
||||
return **value;
|
||||
else
|
||||
throw EmptyError(name);
|
||||
}
|
||||
/// Note this does not throw on assignment, though
|
||||
/// afterwards it seems to work fine. Best to use
|
||||
/// explicit * notation.
|
||||
T& operator *() const {
|
||||
return get();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
53
scripts/MakeSingleHeader.py
Executable file
53
scripts/MakeSingleHeader.py
Executable file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Requires Python 3.6
|
||||
|
||||
from plumbum import local, cli, FG
|
||||
import re
|
||||
|
||||
includes_local = re.compile(r"""^#include "(.*)"$""", re.MULTILINE)
|
||||
includes_system = re.compile(r"""^#include \<(.*)\>$""", re.MULTILINE)
|
||||
|
||||
DIR = local.path(__file__).dirname
|
||||
BDIR = DIR / '../include'
|
||||
|
||||
class MakeHeader(cli.Application):
|
||||
|
||||
def main(self, out : cli.NonexistentPath = BDIR / 'CLI11.hpp'):
|
||||
main_header = BDIR / 'CLI/CLI.hpp'
|
||||
header = main_header.read()
|
||||
|
||||
include_files = includes_local.findall(header)
|
||||
|
||||
headers = set()
|
||||
output = ''
|
||||
with open('output.hpp', 'w') as f:
|
||||
for inc in include_files:
|
||||
inner = (BDIR / inc).read()
|
||||
headers |= set(includes_system.findall(inner))
|
||||
output += f'\n// From {inc}\n\n'
|
||||
output += inner[inner.find('namespace'):]
|
||||
|
||||
header_list = '\n'.join(f'#include <{h}>' for h in headers)
|
||||
|
||||
output = f'''\
|
||||
#pragma once
|
||||
|
||||
// Distributed under the LGPL version 3.0 license. See accompanying
|
||||
// file LICENSE or https://github.com/henryiii/CLI11 for details.
|
||||
|
||||
// This file was generated using MakeSingleHeader.py in CLI11/scripts
|
||||
// This has the complete CLI library in one file.
|
||||
|
||||
{header_list}
|
||||
{output}'''
|
||||
|
||||
with out.open('w') as f:
|
||||
f.write(output)
|
||||
|
||||
print(f"Created {out}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
MakeHeader()
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
#include "CLI.hpp"
|
||||
#include "CLI/CLI.hpp"
|
||||
#include "gtest/gtest.h"
|
||||
#include <fstream>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "CLI.hpp"
|
||||
#include "CLI/CLI.hpp"
|
||||
#include "gtest/gtest.h"
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
|
Loading…
x
Reference in New Issue
Block a user