1
0
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:
Henry Fredrick Schreiner 2017-02-06 09:54:16 -05:00
parent 44a27be54f
commit a12a94c4c1
14 changed files with 817 additions and 614 deletions

View File

@ -1,4 +1,4 @@
#include "CLI.hpp"
#include "CLI/CLI.hpp"
int main (int argc, char** argv) {

View File

@ -1,4 +1,4 @@
#include "CLI.hpp"
#include "CLI/CLI.hpp"
int main (int argc, char** argv) {

View File

@ -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 &current, 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 &current, 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
View 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
View 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
View 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
View 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
View 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 &current, 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 &current, 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);
}
}
}

View 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
View 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
View 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
View 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()

View File

@ -1,5 +1,5 @@
#include "CLI.hpp"
#include "CLI/CLI.hpp"
#include "gtest/gtest.h"
#include <fstream>

View File

@ -1,4 +1,4 @@
#include "CLI.hpp"
#include "CLI/CLI.hpp"
#include "gtest/gtest.h"
#include <cstdio>
#include <fstream>