mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-05-01 21:23:52 +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) {
|
int main (int argc, char** argv) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#include "CLI.hpp"
|
#include "CLI/CLI.hpp"
|
||||||
|
|
||||||
|
|
||||||
int main (int argc, char** argv) {
|
int main (int argc, char** argv) {
|
||||||
|
@ -8,628 +8,26 @@
|
|||||||
#include <deque>
|
#include <deque>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <tuple>
|
|
||||||
#include <exception>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <type_traits>
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <iomanip>
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <vector>
|
#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 {
|
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};
|
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.
|
/// 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
|
/** 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
|
* add_option methods make it easy to prepare options. Remember to call `.start` before starting your
|
||||||
@ -1391,4 +789,6 @@ public:
|
|||||||
return name;
|
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 "gtest/gtest.h"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#include "CLI.hpp"
|
#include "CLI/CLI.hpp"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user