Commit a12a94c4c1ac34836a4a33646b1b5948901ed854
1 parent
44a27be5
Separate files, plus way to combine
Showing
14 changed files
with
817 additions
and
614 deletions
examples/try.cpp
examples/try1.cpp
include/CLI.hpp renamed to include/CLI/App.hpp
| ... | ... | @@ -8,628 +8,26 @@ |
| 8 | 8 | #include <deque> |
| 9 | 9 | #include <iostream> |
| 10 | 10 | #include <functional> |
| 11 | -#include <tuple> | |
| 12 | -#include <exception> | |
| 13 | -#include <stdexcept> | |
| 14 | 11 | #include <algorithm> |
| 15 | 12 | #include <sstream> |
| 16 | -#include <type_traits> | |
| 17 | 13 | #include <set> |
| 18 | -#include <iomanip> | |
| 19 | 14 | #include <numeric> |
| 20 | 15 | #include <vector> |
| 21 | -#include <locale> | |
| 22 | 16 | |
| 23 | -// C standard library | |
| 24 | -// Only needed for existence checking | |
| 25 | -// Could be swapped for filesystem in C++17 | |
| 26 | -#include <sys/types.h> | |
| 27 | -#include <sys/stat.h> | |
| 28 | 17 | |
| 18 | +// CLI Library includes | |
| 19 | +#include "CLI/Error.hpp" | |
| 20 | +#include "CLI/TypeTools.hpp" | |
| 21 | +#include "CLI/StringTools.hpp" | |
| 22 | +#include "CLI/Split.hpp" | |
| 23 | +#include "CLI/Combiner.hpp" | |
| 24 | +#include "CLI/Option.hpp" | |
| 25 | +#include "CLI/Value.hpp" | |
| 29 | 26 | |
| 30 | 27 | namespace CLI { |
| 31 | 28 | |
| 32 | - | |
| 33 | -// Error definitions | |
| 34 | - | |
| 35 | -struct Error : public std::runtime_error { | |
| 36 | - int exit_code; | |
| 37 | - bool print_help; | |
| 38 | - 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) {} | |
| 39 | -}; | |
| 40 | - | |
| 41 | -struct Success : public Error { | |
| 42 | - Success() : Error("Success", "Successfully completed, should be caught and quit", 0, false) {} | |
| 43 | -}; | |
| 44 | - | |
| 45 | -struct CallForHelp : public Error { | |
| 46 | - CallForHelp() : Error("CallForHelp", "This should be caught in your main function, see examples", 0) {} | |
| 47 | -}; | |
| 48 | - | |
| 49 | -struct BadNameString : public Error { | |
| 50 | - BadNameString(std::string name) : Error("BadNameString", name, 1) {} | |
| 51 | -}; | |
| 52 | - | |
| 53 | - | |
| 54 | -struct ParseError : public Error { | |
| 55 | - ParseError(std::string name) : Error("ParseError", name, 2) {} | |
| 56 | -}; | |
| 57 | - | |
| 58 | -struct OptionAlreadyAdded : public Error { | |
| 59 | - OptionAlreadyAdded(std::string name) : Error("OptionAlreadyAdded", name, 3) {} | |
| 60 | -}; | |
| 61 | - | |
| 62 | -struct OptionNotFound : public Error { | |
| 63 | - OptionNotFound(std::string name) : Error("OptionNotFound", name, 4) {} | |
| 64 | -}; | |
| 65 | - | |
| 66 | -struct RequiredError : public Error { | |
| 67 | - RequiredError(std::string name) : Error("RequiredError", name, 5) {} | |
| 68 | -}; | |
| 69 | - | |
| 70 | -struct PositionalError : public Error { | |
| 71 | - PositionalError(std::string name) : Error("PositionalError", name, 6) {} | |
| 72 | -}; | |
| 73 | - | |
| 74 | -struct HorribleError : public Error { | |
| 75 | - HorribleError(std::string name) : Error("HorribleError", "(You should never see this error) " + name, 7) {} | |
| 76 | -}; | |
| 77 | -struct IncorrectConstruction : public Error { | |
| 78 | - IncorrectConstruction(std::string name) : Error("IncorrectConstruction", name, 8) {} | |
| 79 | -}; | |
| 80 | -struct EmptyError : public Error { | |
| 81 | - EmptyError(std::string name) : Error("EmptyError", name, 9) {} | |
| 82 | -}; | |
| 83 | - | |
| 84 | - | |
| 85 | -// Type tools | |
| 86 | -// | |
| 87 | -// Copied from C++14 | |
| 88 | -#if __cplusplus < 201402L | |
| 89 | -template< bool B, class T = void > | |
| 90 | -using enable_if_t = typename std::enable_if<B,T>::type; | |
| 91 | -#else | |
| 92 | -using std::enable_if_t; | |
| 93 | -#endif | |
| 94 | -// If your compiler supports C++14, you can use that definition instead | |
| 95 | - | |
| 96 | -template <typename T> | |
| 97 | -struct is_vector { | |
| 98 | - static const bool value = false; | |
| 99 | -}; | |
| 100 | - | |
| 101 | - | |
| 102 | -template<class T, class A> | |
| 103 | -struct is_vector<std::vector<T, A> > { | |
| 104 | - static bool const value = true; | |
| 105 | -}; | |
| 106 | - | |
| 107 | -template <typename T> | |
| 108 | -struct is_bool { | |
| 109 | - static const bool value = false; | |
| 110 | -}; | |
| 111 | - | |
| 112 | -template<> | |
| 113 | -struct is_bool<bool> { | |
| 114 | - static bool const value = true; | |
| 115 | -}; | |
| 116 | - | |
| 117 | -namespace detail { | |
| 118 | - // Based generally on https://rmf.io/cxx11/almost-static-if | |
| 119 | - /// Simple empty scoped class | |
| 120 | - enum class enabler {}; | |
| 121 | - | |
| 122 | - /// An instance to use in EnableIf | |
| 123 | - constexpr enabler dummy = {}; | |
| 124 | - | |
| 125 | - /// Simple function to join a string | |
| 126 | - template <typename T> | |
| 127 | - std::string join(const T& v, std::string delim = ",") { | |
| 128 | - std::ostringstream s; | |
| 129 | - size_t start = 0; | |
| 130 | - for (const auto& i : v) { | |
| 131 | - if(start++ > 0) | |
| 132 | - s << delim; | |
| 133 | - s << i; | |
| 134 | - } | |
| 135 | - return s.str(); | |
| 136 | - } | |
| 137 | - | |
| 138 | - /// Was going to be based on | |
| 139 | - /// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template | |
| 140 | - /// But this is cleaner and works better in this case | |
| 141 | - | |
| 142 | - template<typename T, | |
| 143 | - enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy> | |
| 144 | - constexpr const char* type_name() { | |
| 145 | - return "INT"; | |
| 146 | - } | |
| 147 | - | |
| 148 | - template<typename T, | |
| 149 | - enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy> | |
| 150 | - constexpr const char* type_name() { | |
| 151 | - return "UINT"; | |
| 152 | - } | |
| 153 | - | |
| 154 | - | |
| 155 | - template<typename T, | |
| 156 | - enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> | |
| 157 | - constexpr const char* type_name() { | |
| 158 | - return "FLOAT"; | |
| 159 | - } | |
| 160 | - | |
| 161 | - | |
| 162 | - /// This one should not be used, since vector types print the internal type | |
| 163 | - template<typename T, | |
| 164 | - enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy> | |
| 165 | - constexpr const char* type_name() { | |
| 166 | - return "VECTOR"; | |
| 167 | - } | |
| 168 | - | |
| 169 | - | |
| 170 | - template<typename T, | |
| 171 | - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value | |
| 172 | - , detail::enabler> = detail::dummy> | |
| 173 | - constexpr const char* type_name() { | |
| 174 | - return "STRING"; | |
| 175 | - } | |
| 176 | - | |
| 177 | - void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) { | |
| 178 | - name = " " + name; | |
| 179 | - out << std::setw(wid) << std::left << name; | |
| 180 | - if(description != "") { | |
| 181 | - if(name.length()>=wid) | |
| 182 | - out << std::endl << std::setw(wid) << ""; | |
| 183 | - out << description << std::endl; | |
| 184 | - } | |
| 185 | - } | |
| 186 | - | |
| 187 | - struct Combiner { | |
| 188 | - int num; | |
| 189 | - bool required; | |
| 190 | - bool defaulted; | |
| 191 | - std::vector<std::function<bool(std::string)>> validators; | |
| 192 | - | |
| 193 | - /// Can be or-ed together | |
| 194 | - Combiner operator | (Combiner b) const { | |
| 195 | - Combiner self; | |
| 196 | - self.num = std::min(num, b.num) == -1 ? -1 : std::max(num, b.num); | |
| 197 | - self.required = required || b.required; | |
| 198 | - self.defaulted = defaulted || b.defaulted; | |
| 199 | - self.validators.reserve(validators.size() + b.validators.size()); | |
| 200 | - self.validators.insert(self.validators.end(), validators.begin(), validators.end()); | |
| 201 | - self.validators.insert(self.validators.end(), b.validators.begin(), b.validators.end()); | |
| 202 | - return self; | |
| 203 | - } | |
| 204 | - | |
| 205 | - /// Call to give the number of arguments expected on cli | |
| 206 | - Combiner operator() (int n) const { | |
| 207 | - Combiner self = *this; | |
| 208 | - self.num = n; | |
| 209 | - return self; | |
| 210 | - } | |
| 211 | - /// Call to give a validator | |
| 212 | - Combiner operator() (std::function<bool(std::string)> func) const { | |
| 213 | - Combiner self = *this; | |
| 214 | - self.validators.push_back(func); | |
| 215 | - return self; | |
| 216 | - } | |
| 217 | - }; | |
| 218 | - | |
| 219 | - bool _ExistingFile(std::string filename) { | |
| 220 | - // std::fstream f(name.c_str()); | |
| 221 | - // return f.good(); | |
| 222 | - // Fastest way according to http://stackoverflow.com/questions/12774207/fastest-way-to-check-if-a-file-exist-using-standard-c-c11-c | |
| 223 | - struct stat buffer; | |
| 224 | - return (stat(filename.c_str(), &buffer) == 0); | |
| 225 | - } | |
| 226 | - | |
| 227 | - bool _ExistingDirectory(std::string filename) { | |
| 228 | - struct stat buffer; | |
| 229 | - if(stat(filename.c_str(), &buffer) == 0 && (buffer.st_mode & S_IFDIR) ) | |
| 230 | - return true; | |
| 231 | - return false; | |
| 232 | - } | |
| 233 | - | |
| 234 | - bool _NonexistentPath(std::string filename) { | |
| 235 | - struct stat buffer; | |
| 236 | - return stat(filename.c_str(), &buffer) != 0; | |
| 237 | - } | |
| 238 | - | |
| 239 | - | |
| 240 | - template<typename T> | |
| 241 | - bool valid_first_char(T c) { | |
| 242 | - return std::isalpha(c) || c=='_'; | |
| 243 | - } | |
| 244 | - | |
| 245 | - template<typename T> | |
| 246 | - bool valid_later_char(T c) { | |
| 247 | - return std::isalnum(c) || c=='_' || c=='.' || c=='-'; | |
| 248 | - } | |
| 249 | - | |
| 250 | - inline bool valid_name_string(const std::string &str) { | |
| 251 | - if(str.size()<1 || !valid_first_char(str[0])) | |
| 252 | - return false; | |
| 253 | - for(auto c : str.substr(1)) | |
| 254 | - if(!valid_later_char(c)) | |
| 255 | - return false; | |
| 256 | - return true; | |
| 257 | - } | |
| 258 | - | |
| 259 | - // Returns false if not a short option. Otherwise, sets opt name and rest and returns true | |
| 260 | - inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { | |
| 261 | - if(current.size()>1 && current[0] == '-' && valid_first_char(current[1])) { | |
| 262 | - name = current.substr(1,1); | |
| 263 | - rest = current.substr(2); | |
| 264 | - return true; | |
| 265 | - } else | |
| 266 | - return false; | |
| 267 | - } | |
| 268 | - | |
| 269 | - // Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true | |
| 270 | - inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { | |
| 271 | - if(current.size()>2 && current.substr(0,2) == "--" && valid_first_char(current[2])) { | |
| 272 | - auto loc = current.find("="); | |
| 273 | - if(loc != std::string::npos) { | |
| 274 | - name = current.substr(2,loc-2); | |
| 275 | - value = current.substr(loc+1); | |
| 276 | - } else { | |
| 277 | - name = current.substr(2); | |
| 278 | - value = ""; | |
| 279 | - } | |
| 280 | - return true; | |
| 281 | - } else | |
| 282 | - return false; | |
| 283 | - } | |
| 284 | - | |
| 285 | - // Splits a string into multiple long and short names | |
| 286 | - inline std::vector<std::string> split_names(std::string current) { | |
| 287 | - std::vector<std::string> output; | |
| 288 | - size_t val; | |
| 289 | - while((val = current.find(",")) != std::string::npos) { | |
| 290 | - output.push_back(current.substr(0,val)); | |
| 291 | - current = current.substr(val+1); | |
| 292 | - } | |
| 293 | - output.push_back(current); | |
| 294 | - return output; | |
| 295 | - | |
| 296 | - } | |
| 297 | - | |
| 298 | - | |
| 299 | - inline std::tuple<std::vector<std::string>,std::vector<std::string>, std::string> | |
| 300 | - get_names(const std::vector<std::string> &input) { | |
| 301 | - | |
| 302 | - std::vector<std::string> short_names; | |
| 303 | - std::vector<std::string> long_names; | |
| 304 | - std::string pos_name; | |
| 305 | - | |
| 306 | - for(std::string name : input) { | |
| 307 | - if(name.length() == 0) | |
| 308 | - continue; | |
| 309 | - else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { | |
| 310 | - if(name.length()==2 && valid_first_char(name[1])) | |
| 311 | - short_names.push_back(std::string(1,name[1])); | |
| 312 | - else | |
| 313 | - throw BadNameString("Invalid one char name: "+name); | |
| 314 | - } else if(name.length() > 2 && name.substr(0,2) == "--") { | |
| 315 | - name = name.substr(2); | |
| 316 | - if(valid_name_string(name)) | |
| 317 | - long_names.push_back(name); | |
| 318 | - else | |
| 319 | - throw BadNameString("Bad long name: "+name); | |
| 320 | - } else if(name == "-" || name == "--") { | |
| 321 | - throw BadNameString("Must have a name, not just dashes"); | |
| 322 | - } else { | |
| 323 | - if(pos_name.length() > 0) | |
| 324 | - throw BadNameString("Only one positional name allowed, remove: "+name); | |
| 325 | - pos_name = name; | |
| 326 | - | |
| 327 | - } | |
| 328 | - } | |
| 329 | - | |
| 330 | - return std::tuple<std::vector<std::string>,std::vector<std::string>, std::string> | |
| 331 | - (short_names, long_names, pos_name); | |
| 332 | - } | |
| 333 | - | |
| 334 | - // Integers | |
| 335 | - template<typename T, enable_if_t<std::is_integral<T>::value, detail::enabler> = detail::dummy> | |
| 336 | - bool lexical_cast(std::string input, T& output) { | |
| 337 | - try{ | |
| 338 | - output = (T) std::stoll(input); | |
| 339 | - return true; | |
| 340 | - } catch (std::invalid_argument) { | |
| 341 | - return false; | |
| 342 | - } catch (std::out_of_range) { | |
| 343 | - return false; | |
| 344 | - } | |
| 345 | - } | |
| 346 | - | |
| 347 | - // Floats | |
| 348 | - template<typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> | |
| 349 | - bool lexical_cast(std::string input, T& output) { | |
| 350 | - try{ | |
| 351 | - output = (T) std::stold(input); | |
| 352 | - return true; | |
| 353 | - } catch (std::invalid_argument) { | |
| 354 | - return false; | |
| 355 | - } catch (std::out_of_range) { | |
| 356 | - return false; | |
| 357 | - } | |
| 358 | - } | |
| 359 | - | |
| 360 | - // Vector | |
| 361 | - template<typename T, | |
| 362 | - enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy> | |
| 363 | - bool lexical_cast(std::string input, T& output) { | |
| 364 | - if(output.size() == input.size()) | |
| 365 | - output.resize(input.size()); | |
| 366 | - for(size_t i=0; i<input.size(); i++) | |
| 367 | - output[i] = input[i]; | |
| 368 | - return true; | |
| 369 | - } | |
| 370 | - | |
| 371 | - // String and similar | |
| 372 | - template<typename T, | |
| 373 | - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value | |
| 374 | - , detail::enabler> = detail::dummy> | |
| 375 | - bool lexical_cast(std::string input, T& output) { | |
| 376 | - output = input; | |
| 377 | - return true; | |
| 378 | - } | |
| 379 | -} | |
| 380 | - | |
| 381 | - | |
| 382 | - | |
| 383 | -// Defines for common Combiners (don't use combiners directly) | |
| 384 | - | |
| 385 | -const detail::Combiner Nothing {0, false, false, {}}; | |
| 386 | -const detail::Combiner Required {1, true, false, {}}; | |
| 387 | -const detail::Combiner Default {1, false, true, {}}; | |
| 388 | -const detail::Combiner Args {-1, false, false, {}}; | |
| 389 | -const detail::Combiner Validators {1, false, false, {}}; | |
| 390 | - | |
| 391 | -// Warning about using these validators: | |
| 392 | -// The files could be added/deleted after the validation. This is not common, | |
| 393 | -// but if this is a possibility, check the file you open afterwards | |
| 394 | -const detail::Combiner ExistingFile {1, false, false, {detail::_ExistingFile}}; | |
| 395 | -const detail::Combiner ExistingDirectory {1, false, false, {detail::_ExistingDirectory}}; | |
| 396 | -const detail::Combiner NonexistentPath {1, false, false, {detail::_NonexistentPath}}; | |
| 397 | - | |
| 398 | -typedef std::vector<std::vector<std::string>> results_t; | |
| 399 | -typedef std::function<bool(results_t)> callback_t; | |
| 400 | - | |
| 401 | - | |
| 402 | -class App; | |
| 403 | - | |
| 404 | -class Option { | |
| 405 | - friend App; | |
| 406 | -protected: | |
| 407 | - // Config | |
| 408 | - std::vector<std::string> snames; | |
| 409 | - std::vector<std::string> lnames; | |
| 410 | - std::string pname; | |
| 411 | - | |
| 412 | - detail::Combiner opts; | |
| 413 | - std::string description; | |
| 414 | - callback_t callback; | |
| 415 | - | |
| 416 | - // These are for help strings | |
| 417 | - std::string defaultval; | |
| 418 | - std::string typeval; | |
| 419 | - | |
| 420 | - // Results | |
| 421 | - results_t results {}; | |
| 422 | - | |
| 423 | - | |
| 424 | -public: | |
| 425 | - Option(std::string name, std::string description = "", detail::Combiner opts=Nothing, std::function<bool(results_t)> callback=[](results_t){return true;}) : | |
| 426 | - opts(opts), description(description), callback(callback){ | |
| 427 | - std::tie(snames, lnames, pname) = detail::get_names(detail::split_names(name)); | |
| 428 | - } | |
| 429 | - | |
| 430 | - /// Clear the parsed results (mostly for testing) | |
| 431 | - void clear() { | |
| 432 | - results.clear(); | |
| 433 | - } | |
| 434 | - | |
| 435 | - /// True if option is required | |
| 436 | - bool required() const { | |
| 437 | - return opts.required; | |
| 438 | - } | |
| 439 | - | |
| 440 | - /// The number of arguments the option expects | |
| 441 | - int expected() const { | |
| 442 | - return opts.num; | |
| 443 | - } | |
| 444 | - | |
| 445 | - /// True if the argument can be given directly | |
| 446 | - bool positional() const { | |
| 447 | - return pname.length() > 0; | |
| 448 | - } | |
| 449 | - | |
| 450 | - /// True if option has at least one non-positional name | |
| 451 | - bool nonpositional() const { | |
| 452 | - return (snames.size() + lnames.size()) > 0; | |
| 453 | - } | |
| 454 | - | |
| 455 | - /// True if this should print the default string | |
| 456 | - bool defaulted() const { | |
| 457 | - return opts.defaulted; | |
| 458 | - } | |
| 459 | - | |
| 460 | - /// True if option has description | |
| 461 | - bool has_description() const { | |
| 462 | - return description.length() > 0; | |
| 463 | - } | |
| 464 | - | |
| 465 | - /// Get the description | |
| 466 | - const std::string& get_description() const { | |
| 467 | - return description; | |
| 468 | - } | |
| 469 | - | |
| 470 | - /// The name and any extras needed for positionals | |
| 471 | - std::string help_positional() const { | |
| 472 | - std::string out = pname; | |
| 473 | - if(expected()<1) | |
| 474 | - out = out + "x" + std::to_string(expected()); | |
| 475 | - else if(expected()==-1) | |
| 476 | - out = out + "..."; | |
| 477 | - out = required() ? out : "["+out+"]"; | |
| 478 | - return out; | |
| 479 | - } | |
| 480 | - | |
| 481 | - // Just the pname | |
| 482 | - std::string get_pname() const { | |
| 483 | - return pname; | |
| 484 | - } | |
| 485 | - | |
| 486 | - /// Process the callback | |
| 487 | - bool run_callback() const { | |
| 488 | - if(opts.validators.size()>0) { | |
| 489 | - for(const std::string & result : flatten_results()) | |
| 490 | - for(const std::function<bool(std::string)> &vali : opts.validators) | |
| 491 | - if(!vali(result)) | |
| 492 | - return false; | |
| 493 | - } | |
| 494 | - return callback(results); | |
| 495 | - } | |
| 496 | - | |
| 497 | - /// If options share any of the same names, they are equal (not counting positional) | |
| 498 | - bool operator== (const Option& other) const { | |
| 499 | - for(const std::string &sname : snames) | |
| 500 | - for(const std::string &othersname : other.snames) | |
| 501 | - if(sname == othersname) | |
| 502 | - return true; | |
| 503 | - for(const std::string &lname : lnames) | |
| 504 | - for(const std::string &otherlname : other.lnames) | |
| 505 | - if(lname == otherlname) | |
| 506 | - return true; | |
| 507 | - return false; | |
| 508 | - } | |
| 509 | - | |
| 510 | - /// Gets a , sep list of names. Does not include the positional name. | |
| 511 | - std::string get_name() const { | |
| 512 | - std::vector<std::string> name_list; | |
| 513 | - for(const std::string& sname : snames) | |
| 514 | - name_list.push_back("-"+sname); | |
| 515 | - for(const std::string& lname : lnames) | |
| 516 | - name_list.push_back("--"+lname); | |
| 517 | - return detail::join(name_list); | |
| 518 | - } | |
| 519 | - | |
| 520 | - /// Check a name. Requires "-" or "--" for short / long, supports positional name | |
| 521 | - bool check_name(std::string name) const { | |
| 522 | - | |
| 523 | - if(name.length()>2 && name.substr(0,2) == "--") | |
| 524 | - return check_lname(name.substr(2)); | |
| 525 | - else if (name.length()>1 && name.substr(0,1) == "-") | |
| 526 | - return check_sname(name.substr(1)); | |
| 527 | - else | |
| 528 | - return name == pname; | |
| 529 | - } | |
| 530 | - | |
| 531 | - /// Requires "-" to be removed from string | |
| 532 | - bool check_sname(const std::string& name) const { | |
| 533 | - return std::find(std::begin(snames), std::end(snames), name) != std::end(snames); | |
| 534 | - } | |
| 535 | - | |
| 536 | - /// Requires "--" to be removed from string | |
| 537 | - bool check_lname(const std::string& name) const { | |
| 538 | - return std::find(std::begin(lnames), std::end(lnames), name) != std::end(lnames); | |
| 539 | - } | |
| 540 | - | |
| 541 | - | |
| 542 | - /// Puts a result at position r | |
| 543 | - void add_result(int r, std::string s) { | |
| 544 | - results.at(r).push_back(s); | |
| 545 | - } | |
| 546 | - | |
| 547 | - /// Starts a new results vector (used for r in add_result) | |
| 548 | - int get_new() { | |
| 549 | - results.emplace_back(); | |
| 550 | - return results.size() - 1; | |
| 551 | - } | |
| 552 | - | |
| 553 | - /// Count the total number of times an option was passed | |
| 554 | - int count() const { | |
| 555 | - int out = 0; | |
| 556 | - for(const std::vector<std::string>& v : results) | |
| 557 | - out += v.size(); | |
| 558 | - return out; | |
| 559 | - } | |
| 560 | - | |
| 561 | - /// Diagnostic representation | |
| 562 | - std::string string() const { | |
| 563 | - std::string val = "Option: " + get_name() + "\n" | |
| 564 | - + " " + description + "\n" | |
| 565 | - + " ["; | |
| 566 | - for(const auto& item : results) { | |
| 567 | - if(&item!=&results[0]) | |
| 568 | - val+="],["; | |
| 569 | - val += detail::join(item); | |
| 570 | - } | |
| 571 | - val += "]"; | |
| 572 | - return val; | |
| 573 | - } | |
| 574 | - | |
| 575 | - /// The first half of the help print, name plus default, etc | |
| 576 | - std::string help_name() const { | |
| 577 | - std::stringstream out; | |
| 578 | - out << get_name(); | |
| 579 | - if(expected() != 0) { | |
| 580 | - if(typeval != "") | |
| 581 | - out << " " << typeval; | |
| 582 | - if(defaultval != "") | |
| 583 | - out << "=" << defaultval; | |
| 584 | - if(expected() > 1) | |
| 585 | - out << " x " << expected(); | |
| 586 | - if(expected() == -1) | |
| 587 | - out << " ..."; | |
| 588 | - } | |
| 589 | - return out.str(); | |
| 590 | - } | |
| 591 | - | |
| 592 | - /// Produce a flattened vector of results, vs. a vector of vectors. | |
| 593 | - std::vector<std::string> flatten_results() const { | |
| 594 | - std::vector<std::string> output; | |
| 595 | - for(const std::vector<std::string> result : results) | |
| 596 | - output.insert(std::end(output), std::begin(result), std::end(result)); | |
| 597 | - return output; | |
| 598 | - } | |
| 599 | - | |
| 600 | -}; | |
| 601 | - | |
| 602 | - | |
| 603 | - | |
| 604 | 29 | enum class Classifer {NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND}; |
| 605 | 30 | |
| 606 | - | |
| 607 | -// Prototype return value test | |
| 608 | -template <typename T> | |
| 609 | -class Value { | |
| 610 | - friend App; | |
| 611 | -protected: | |
| 612 | - std::shared_ptr<std::unique_ptr<T>> value {new std::unique_ptr<T>()}; | |
| 613 | - std::string name; | |
| 614 | -public: | |
| 615 | - Value(std::string name) : name(name) {} | |
| 616 | - | |
| 617 | - operator bool() const {return (bool) *value;} | |
| 618 | - | |
| 619 | - T& get() const { | |
| 620 | - if(*value) | |
| 621 | - return **value; | |
| 622 | - else | |
| 623 | - throw EmptyError(name); | |
| 624 | - } | |
| 625 | - /// Note this does not throw on assignment, though | |
| 626 | - /// afterwards it seems to work fine. Best to use | |
| 627 | - /// explicit * notation. | |
| 628 | - T& operator *() const { | |
| 629 | - return get(); | |
| 630 | - } | |
| 631 | -}; | |
| 632 | - | |
| 633 | 31 | /// Creates a command line program, with very few defaults. |
| 634 | 32 | /** To use, create a new Program() instance with argc, argv, and a help description. The templated |
| 635 | 33 | * add_option methods make it easy to prepare options. Remember to call `.start` before starting your |
| ... | ... | @@ -1391,4 +789,6 @@ public: |
| 1391 | 789 | return name; |
| 1392 | 790 | } |
| 1393 | 791 | }; |
| 792 | + | |
| 793 | + | |
| 1394 | 794 | } | ... | ... |
include/CLI/CLI.hpp
0 → 100644
| 1 | +#pragma once | |
| 2 | + | |
| 3 | +// Distributed under the LGPL version 3.0 license. See accompanying | |
| 4 | +// file LICENSE or https://github.com/henryiii/CLI11 for details. | |
| 5 | + | |
| 6 | +// CLI Library includes | |
| 7 | +#include "CLI/Error.hpp" | |
| 8 | +#include "CLI/TypeTools.hpp" | |
| 9 | +#include "CLI/StringTools.hpp" | |
| 10 | +#include "CLI/Split.hpp" | |
| 11 | +#include "CLI/Combiner.hpp" | |
| 12 | +#include "CLI/Option.hpp" | |
| 13 | +#include "CLI/Value.hpp" | |
| 14 | +#include "CLI/App.hpp" | |
| 15 | + | ... | ... |
include/CLI/Combiner.hpp
0 → 100644
| 1 | +#pragma once | |
| 2 | + | |
| 3 | +// Distributed under the LGPL version 3.0 license. See accompanying | |
| 4 | +// file LICENSE or https://github.com/henryiii/CLI11 for details. | |
| 5 | + | |
| 6 | +#include <string> | |
| 7 | +#include <functional> | |
| 8 | +#include <vector> | |
| 9 | + | |
| 10 | + | |
| 11 | +// C standard library | |
| 12 | +// Only needed for existence checking | |
| 13 | +// Could be swapped for filesystem in C++17 | |
| 14 | +#include <sys/types.h> | |
| 15 | +#include <sys/stat.h> | |
| 16 | + | |
| 17 | +namespace CLI { | |
| 18 | + | |
| 19 | +namespace detail { | |
| 20 | + | |
| 21 | +struct Combiner { | |
| 22 | + int num; | |
| 23 | + bool required; | |
| 24 | + bool defaulted; | |
| 25 | + std::vector<std::function<bool(std::string)>> validators; | |
| 26 | + | |
| 27 | + /// Can be or-ed together | |
| 28 | + Combiner operator | (Combiner b) const { | |
| 29 | + Combiner self; | |
| 30 | + self.num = std::min(num, b.num) == -1 ? -1 : std::max(num, b.num); | |
| 31 | + self.required = required || b.required; | |
| 32 | + self.defaulted = defaulted || b.defaulted; | |
| 33 | + self.validators.reserve(validators.size() + b.validators.size()); | |
| 34 | + self.validators.insert(self.validators.end(), validators.begin(), validators.end()); | |
| 35 | + self.validators.insert(self.validators.end(), b.validators.begin(), b.validators.end()); | |
| 36 | + return self; | |
| 37 | + } | |
| 38 | + | |
| 39 | + /// Call to give the number of arguments expected on cli | |
| 40 | + Combiner operator() (int n) const { | |
| 41 | + Combiner self = *this; | |
| 42 | + self.num = n; | |
| 43 | + return self; | |
| 44 | + } | |
| 45 | + /// Call to give a validator | |
| 46 | + Combiner operator() (std::function<bool(std::string)> func) const { | |
| 47 | + Combiner self = *this; | |
| 48 | + self.validators.push_back(func); | |
| 49 | + return self; | |
| 50 | + } | |
| 51 | +}; | |
| 52 | + | |
| 53 | +/// Check for an existing file | |
| 54 | +bool _ExistingFile(std::string filename) { | |
| 55 | +// std::fstream f(name.c_str()); | |
| 56 | +// return f.good(); | |
| 57 | +// Fastest way according to http://stackoverflow.com/questions/12774207/fastest-way-to-check-if-a-file-exist-using-standard-c-c11-c | |
| 58 | + struct stat buffer; | |
| 59 | + return (stat(filename.c_str(), &buffer) == 0); | |
| 60 | +} | |
| 61 | + | |
| 62 | +/// Check for an existing directory | |
| 63 | +bool _ExistingDirectory(std::string filename) { | |
| 64 | + struct stat buffer; | |
| 65 | + if(stat(filename.c_str(), &buffer) == 0 && (buffer.st_mode & S_IFDIR) ) | |
| 66 | + return true; | |
| 67 | + return false; | |
| 68 | +} | |
| 69 | + | |
| 70 | +/// Check for a non-existing path | |
| 71 | +bool _NonexistentPath(std::string filename) { | |
| 72 | + struct stat buffer; | |
| 73 | + return stat(filename.c_str(), &buffer) != 0; | |
| 74 | +} | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | +} | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | +// Defines for common Combiners (don't use combiners directly) | |
| 84 | + | |
| 85 | +const detail::Combiner Nothing {0, false, false, {}}; | |
| 86 | +const detail::Combiner Required {1, true, false, {}}; | |
| 87 | +const detail::Combiner Default {1, false, true, {}}; | |
| 88 | +const detail::Combiner Args {-1, false, false, {}}; | |
| 89 | +const detail::Combiner Validators {1, false, false, {}}; | |
| 90 | + | |
| 91 | +// Warning about using these validators: | |
| 92 | +// The files could be added/deleted after the validation. This is not common, | |
| 93 | +// but if this is a possibility, check the file you open afterwards | |
| 94 | +const detail::Combiner ExistingFile {1, false, false, {detail::_ExistingFile}}; | |
| 95 | +const detail::Combiner ExistingDirectory {1, false, false, {detail::_ExistingDirectory}}; | |
| 96 | +const detail::Combiner NonexistentPath {1, false, false, {detail::_NonexistentPath}}; | |
| 97 | + | |
| 98 | + | |
| 99 | +} | ... | ... |
include/CLI/Error.hpp
0 → 100644
| 1 | +#pragma once | |
| 2 | + | |
| 3 | +// Distributed under the LGPL version 3.0 license. See accompanying | |
| 4 | +// file LICENSE or https://github.com/henryiii/CLI11 for details. | |
| 5 | + | |
| 6 | +#include <string> | |
| 7 | +#include <exception> | |
| 8 | +#include <stdexcept> | |
| 9 | + | |
| 10 | +namespace CLI { | |
| 11 | + | |
| 12 | +// Error definitions | |
| 13 | + | |
| 14 | + | |
| 15 | +struct Error : public std::runtime_error { | |
| 16 | + int exit_code; | |
| 17 | + bool print_help; | |
| 18 | + 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) {} | |
| 19 | +}; | |
| 20 | + | |
| 21 | +struct Success : public Error { | |
| 22 | + Success() : Error("Success", "Successfully completed, should be caught and quit", 0, false) {} | |
| 23 | +}; | |
| 24 | + | |
| 25 | +struct CallForHelp : public Error { | |
| 26 | + CallForHelp() : Error("CallForHelp", "This should be caught in your main function, see examples", 0) {} | |
| 27 | +}; | |
| 28 | + | |
| 29 | +struct BadNameString : public Error { | |
| 30 | + BadNameString(std::string name) : Error("BadNameString", name, 1) {} | |
| 31 | +}; | |
| 32 | + | |
| 33 | + | |
| 34 | +struct ParseError : public Error { | |
| 35 | + ParseError(std::string name) : Error("ParseError", name, 2) {} | |
| 36 | +}; | |
| 37 | + | |
| 38 | +struct OptionAlreadyAdded : public Error { | |
| 39 | + OptionAlreadyAdded(std::string name) : Error("OptionAlreadyAdded", name, 3) {} | |
| 40 | +}; | |
| 41 | + | |
| 42 | +struct OptionNotFound : public Error { | |
| 43 | + OptionNotFound(std::string name) : Error("OptionNotFound", name, 4) {} | |
| 44 | +}; | |
| 45 | + | |
| 46 | +struct RequiredError : public Error { | |
| 47 | + RequiredError(std::string name) : Error("RequiredError", name, 5) {} | |
| 48 | +}; | |
| 49 | + | |
| 50 | +struct PositionalError : public Error { | |
| 51 | + PositionalError(std::string name) : Error("PositionalError", name, 6) {} | |
| 52 | +}; | |
| 53 | + | |
| 54 | +struct HorribleError : public Error { | |
| 55 | + HorribleError(std::string name) : Error("HorribleError", "(You should never see this error) " + name, 7) {} | |
| 56 | +}; | |
| 57 | +struct IncorrectConstruction : public Error { | |
| 58 | + IncorrectConstruction(std::string name) : Error("IncorrectConstruction", name, 8) {} | |
| 59 | +}; | |
| 60 | +struct EmptyError : public Error { | |
| 61 | + EmptyError(std::string name) : Error("EmptyError", name, 9) {} | |
| 62 | +}; | |
| 63 | + | |
| 64 | +} | ... | ... |
include/CLI/Option.hpp
0 → 100644
| 1 | +#pragma once | |
| 2 | + | |
| 3 | +// Distributed under the LGPL version 3.0 license. See accompanying | |
| 4 | +// file LICENSE or https://github.com/henryiii/CLI11 for details. | |
| 5 | + | |
| 6 | +#include <string> | |
| 7 | +#include <functional> | |
| 8 | +#include <vector> | |
| 9 | +#include <tuple> | |
| 10 | +#include <algorithm> | |
| 11 | + | |
| 12 | +#include "CLI/StringTools.hpp" | |
| 13 | +#include "CLI/Split.hpp" | |
| 14 | +#include "CLI/Combiner.hpp" | |
| 15 | + | |
| 16 | +namespace CLI { | |
| 17 | + | |
| 18 | +typedef std::vector<std::vector<std::string>> results_t; | |
| 19 | +typedef std::function<bool(results_t)> callback_t; | |
| 20 | + | |
| 21 | + | |
| 22 | +class App; | |
| 23 | + | |
| 24 | +class Option { | |
| 25 | + friend App; | |
| 26 | +protected: | |
| 27 | + // Config | |
| 28 | + std::vector<std::string> snames; | |
| 29 | + std::vector<std::string> lnames; | |
| 30 | + std::string pname; | |
| 31 | + | |
| 32 | + detail::Combiner opts; | |
| 33 | + std::string description; | |
| 34 | + callback_t callback; | |
| 35 | + | |
| 36 | + // These are for help strings | |
| 37 | + std::string defaultval; | |
| 38 | + std::string typeval; | |
| 39 | + | |
| 40 | + // Results | |
| 41 | + results_t results {}; | |
| 42 | + | |
| 43 | + | |
| 44 | +public: | |
| 45 | + Option(std::string name, std::string description = "", detail::Combiner opts=Nothing, std::function<bool(results_t)> callback=[](results_t){return true;}) : | |
| 46 | + opts(opts), description(description), callback(callback){ | |
| 47 | + std::tie(snames, lnames, pname) = detail::get_names(detail::split_names(name)); | |
| 48 | + } | |
| 49 | + | |
| 50 | + /// Clear the parsed results (mostly for testing) | |
| 51 | + void clear() { | |
| 52 | + results.clear(); | |
| 53 | + } | |
| 54 | + | |
| 55 | + /// True if option is required | |
| 56 | + bool required() const { | |
| 57 | + return opts.required; | |
| 58 | + } | |
| 59 | + | |
| 60 | + /// The number of arguments the option expects | |
| 61 | + int expected() const { | |
| 62 | + return opts.num; | |
| 63 | + } | |
| 64 | + | |
| 65 | + /// True if the argument can be given directly | |
| 66 | + bool positional() const { | |
| 67 | + return pname.length() > 0; | |
| 68 | + } | |
| 69 | + | |
| 70 | + /// True if option has at least one non-positional name | |
| 71 | + bool nonpositional() const { | |
| 72 | + return (snames.size() + lnames.size()) > 0; | |
| 73 | + } | |
| 74 | + | |
| 75 | + /// True if this should print the default string | |
| 76 | + bool defaulted() const { | |
| 77 | + return opts.defaulted; | |
| 78 | + } | |
| 79 | + | |
| 80 | + /// True if option has description | |
| 81 | + bool has_description() const { | |
| 82 | + return description.length() > 0; | |
| 83 | + } | |
| 84 | + | |
| 85 | + /// Get the description | |
| 86 | + const std::string& get_description() const { | |
| 87 | + return description; | |
| 88 | + } | |
| 89 | + | |
| 90 | + /// The name and any extras needed for positionals | |
| 91 | + std::string help_positional() const { | |
| 92 | + std::string out = pname; | |
| 93 | + if(expected()<1) | |
| 94 | + out = out + "x" + std::to_string(expected()); | |
| 95 | + else if(expected()==-1) | |
| 96 | + out = out + "..."; | |
| 97 | + out = required() ? out : "["+out+"]"; | |
| 98 | + return out; | |
| 99 | + } | |
| 100 | + | |
| 101 | + // Just the pname | |
| 102 | + std::string get_pname() const { | |
| 103 | + return pname; | |
| 104 | + } | |
| 105 | + | |
| 106 | + /// Process the callback | |
| 107 | + bool run_callback() const { | |
| 108 | + if(opts.validators.size()>0) { | |
| 109 | + for(const std::string & result : flatten_results()) | |
| 110 | + for(const std::function<bool(std::string)> &vali : opts.validators) | |
| 111 | + if(!vali(result)) | |
| 112 | + return false; | |
| 113 | + } | |
| 114 | + return callback(results); | |
| 115 | + } | |
| 116 | + | |
| 117 | + /// If options share any of the same names, they are equal (not counting positional) | |
| 118 | + bool operator== (const Option& other) const { | |
| 119 | + for(const std::string &sname : snames) | |
| 120 | + for(const std::string &othersname : other.snames) | |
| 121 | + if(sname == othersname) | |
| 122 | + return true; | |
| 123 | + for(const std::string &lname : lnames) | |
| 124 | + for(const std::string &otherlname : other.lnames) | |
| 125 | + if(lname == otherlname) | |
| 126 | + return true; | |
| 127 | + return false; | |
| 128 | + } | |
| 129 | + | |
| 130 | + /// Gets a , sep list of names. Does not include the positional name. | |
| 131 | + std::string get_name() const { | |
| 132 | + std::vector<std::string> name_list; | |
| 133 | + for(const std::string& sname : snames) | |
| 134 | + name_list.push_back("-"+sname); | |
| 135 | + for(const std::string& lname : lnames) | |
| 136 | + name_list.push_back("--"+lname); | |
| 137 | + return detail::join(name_list); | |
| 138 | + } | |
| 139 | + | |
| 140 | + /// Check a name. Requires "-" or "--" for short / long, supports positional name | |
| 141 | + bool check_name(std::string name) const { | |
| 142 | + | |
| 143 | + if(name.length()>2 && name.substr(0,2) == "--") | |
| 144 | + return check_lname(name.substr(2)); | |
| 145 | + else if (name.length()>1 && name.substr(0,1) == "-") | |
| 146 | + return check_sname(name.substr(1)); | |
| 147 | + else | |
| 148 | + return name == pname; | |
| 149 | + } | |
| 150 | + | |
| 151 | + /// Requires "-" to be removed from string | |
| 152 | + bool check_sname(const std::string& name) const { | |
| 153 | + return std::find(std::begin(snames), std::end(snames), name) != std::end(snames); | |
| 154 | + } | |
| 155 | + | |
| 156 | + /// Requires "--" to be removed from string | |
| 157 | + bool check_lname(const std::string& name) const { | |
| 158 | + return std::find(std::begin(lnames), std::end(lnames), name) != std::end(lnames); | |
| 159 | + } | |
| 160 | + | |
| 161 | + | |
| 162 | + /// Puts a result at position r | |
| 163 | + void add_result(int r, std::string s) { | |
| 164 | + results.at(r).push_back(s); | |
| 165 | + } | |
| 166 | + | |
| 167 | + /// Starts a new results vector (used for r in add_result) | |
| 168 | + int get_new() { | |
| 169 | + results.emplace_back(); | |
| 170 | + return results.size() - 1; | |
| 171 | + } | |
| 172 | + | |
| 173 | + /// Count the total number of times an option was passed | |
| 174 | + int count() const { | |
| 175 | + int out = 0; | |
| 176 | + for(const std::vector<std::string>& v : results) | |
| 177 | + out += v.size(); | |
| 178 | + return out; | |
| 179 | + } | |
| 180 | + | |
| 181 | + /// Diagnostic representation | |
| 182 | + std::string string() const { | |
| 183 | + std::string val = "Option: " + get_name() + "\n" | |
| 184 | + + " " + description + "\n" | |
| 185 | + + " ["; | |
| 186 | + for(const auto& item : results) { | |
| 187 | + if(&item!=&results[0]) | |
| 188 | + val+="],["; | |
| 189 | + val += detail::join(item); | |
| 190 | + } | |
| 191 | + val += "]"; | |
| 192 | + return val; | |
| 193 | + } | |
| 194 | + | |
| 195 | + /// The first half of the help print, name plus default, etc | |
| 196 | + std::string help_name() const { | |
| 197 | + std::stringstream out; | |
| 198 | + out << get_name(); | |
| 199 | + if(expected() != 0) { | |
| 200 | + if(typeval != "") | |
| 201 | + out << " " << typeval; | |
| 202 | + if(defaultval != "") | |
| 203 | + out << "=" << defaultval; | |
| 204 | + if(expected() > 1) | |
| 205 | + out << " x " << expected(); | |
| 206 | + if(expected() == -1) | |
| 207 | + out << " ..."; | |
| 208 | + } | |
| 209 | + return out.str(); | |
| 210 | + } | |
| 211 | + | |
| 212 | + /// Produce a flattened vector of results, vs. a vector of vectors. | |
| 213 | + std::vector<std::string> flatten_results() const { | |
| 214 | + std::vector<std::string> output; | |
| 215 | + for(const std::vector<std::string> result : results) | |
| 216 | + output.insert(std::end(output), std::begin(result), std::end(result)); | |
| 217 | + return output; | |
| 218 | + } | |
| 219 | + | |
| 220 | +}; | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | +} | ... | ... |
include/CLI/Split.hpp
0 → 100644
| 1 | +#pragma once | |
| 2 | + | |
| 3 | +// Distributed under the LGPL version 3.0 license. See accompanying | |
| 4 | +// file LICENSE or https://github.com/henryiii/CLI11 for details. | |
| 5 | + | |
| 6 | +#include <string> | |
| 7 | +#include <vector> | |
| 8 | +#include <tuple> | |
| 9 | + | |
| 10 | +#include "CLI/Error.hpp" | |
| 11 | +#include "CLI/StringTools.hpp" | |
| 12 | + | |
| 13 | +namespace CLI { | |
| 14 | +namespace detail { | |
| 15 | + | |
| 16 | +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true | |
| 17 | +inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { | |
| 18 | + if(current.size()>1 && current[0] == '-' && valid_first_char(current[1])) { | |
| 19 | + name = current.substr(1,1); | |
| 20 | + rest = current.substr(2); | |
| 21 | + return true; | |
| 22 | + } else | |
| 23 | + return false; | |
| 24 | +} | |
| 25 | + | |
| 26 | +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true | |
| 27 | +inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { | |
| 28 | + if(current.size()>2 && current.substr(0,2) == "--" && valid_first_char(current[2])) { | |
| 29 | + auto loc = current.find("="); | |
| 30 | + if(loc != std::string::npos) { | |
| 31 | + name = current.substr(2,loc-2); | |
| 32 | + value = current.substr(loc+1); | |
| 33 | + } else { | |
| 34 | + name = current.substr(2); | |
| 35 | + value = ""; | |
| 36 | + } | |
| 37 | + return true; | |
| 38 | + } else | |
| 39 | + return false; | |
| 40 | +} | |
| 41 | + | |
| 42 | +// Splits a string into multiple long and short names | |
| 43 | +inline std::vector<std::string> split_names(std::string current) { | |
| 44 | + std::vector<std::string> output; | |
| 45 | + size_t val; | |
| 46 | + while((val = current.find(",")) != std::string::npos) { | |
| 47 | + output.push_back(current.substr(0,val)); | |
| 48 | + current = current.substr(val+1); | |
| 49 | + } | |
| 50 | + output.push_back(current); | |
| 51 | + return output; | |
| 52 | + | |
| 53 | +} | |
| 54 | + | |
| 55 | +/// Get a vector of short names, one of long names, and a single name | |
| 56 | +inline std::tuple<std::vector<std::string>,std::vector<std::string>, std::string> | |
| 57 | + get_names(const std::vector<std::string> &input) { | |
| 58 | + | |
| 59 | + std::vector<std::string> short_names; | |
| 60 | + std::vector<std::string> long_names; | |
| 61 | + std::string pos_name; | |
| 62 | + | |
| 63 | + for(std::string name : input) { | |
| 64 | + if(name.length() == 0) | |
| 65 | + continue; | |
| 66 | + else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { | |
| 67 | + if(name.length()==2 && valid_first_char(name[1])) | |
| 68 | + short_names.push_back(std::string(1,name[1])); | |
| 69 | + else | |
| 70 | + throw BadNameString("Invalid one char name: "+name); | |
| 71 | + } else if(name.length() > 2 && name.substr(0,2) == "--") { | |
| 72 | + name = name.substr(2); | |
| 73 | + if(valid_name_string(name)) | |
| 74 | + long_names.push_back(name); | |
| 75 | + else | |
| 76 | + throw BadNameString("Bad long name: "+name); | |
| 77 | + } else if(name == "-" || name == "--") { | |
| 78 | + throw BadNameString("Must have a name, not just dashes"); | |
| 79 | + } else { | |
| 80 | + if(pos_name.length() > 0) | |
| 81 | + throw BadNameString("Only one positional name allowed, remove: "+name); | |
| 82 | + pos_name = name; | |
| 83 | + | |
| 84 | + } | |
| 85 | + } | |
| 86 | + | |
| 87 | + return std::tuple<std::vector<std::string>,std::vector<std::string>, std::string> | |
| 88 | + (short_names, long_names, pos_name); | |
| 89 | +} | |
| 90 | + | |
| 91 | + | |
| 92 | +} | |
| 93 | +} | ... | ... |
include/CLI/StringTools.hpp
0 → 100644
| 1 | +#pragma once | |
| 2 | + | |
| 3 | +// Distributed under the LGPL version 3.0 license. See accompanying | |
| 4 | +// file LICENSE or https://github.com/henryiii/CLI11 for details. | |
| 5 | + | |
| 6 | +#include <string> | |
| 7 | +#include <sstream> | |
| 8 | +#include <iomanip> | |
| 9 | +#include <locale> | |
| 10 | +#include <type_traits> | |
| 11 | + | |
| 12 | +namespace CLI { | |
| 13 | +namespace detail { | |
| 14 | + | |
| 15 | + | |
| 16 | +/// Simple function to join a string | |
| 17 | +template <typename T> | |
| 18 | +std::string join(const T& v, std::string delim = ",") { | |
| 19 | + std::ostringstream s; | |
| 20 | + size_t start = 0; | |
| 21 | + for (const auto& i : v) { | |
| 22 | + if(start++ > 0) | |
| 23 | + s << delim; | |
| 24 | + s << i; | |
| 25 | + } | |
| 26 | + return s.str(); | |
| 27 | +} | |
| 28 | + | |
| 29 | +/// Print a two part "help" string | |
| 30 | +void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) { | |
| 31 | + name = " " + name; | |
| 32 | + out << std::setw(wid) << std::left << name; | |
| 33 | + if(description != "") { | |
| 34 | + if(name.length()>=wid) | |
| 35 | + out << std::endl << std::setw(wid) << ""; | |
| 36 | + out << description << std::endl; | |
| 37 | + } | |
| 38 | +} | |
| 39 | + | |
| 40 | +/// Verify the first character of an option | |
| 41 | +template<typename T> | |
| 42 | +bool valid_first_char(T c) { | |
| 43 | + return std::isalpha(c) || c=='_'; | |
| 44 | +} | |
| 45 | + | |
| 46 | +/// Verify following characters of an option | |
| 47 | +template<typename T> | |
| 48 | +bool valid_later_char(T c) { | |
| 49 | + return std::isalnum(c) || c=='_' || c=='.' || c=='-'; | |
| 50 | +} | |
| 51 | + | |
| 52 | +/// Verify an option name | |
| 53 | +inline bool valid_name_string(const std::string &str) { | |
| 54 | + if(str.size()<1 || !valid_first_char(str[0])) | |
| 55 | + return false; | |
| 56 | + for(auto c : str.substr(1)) | |
| 57 | + if(!valid_later_char(c)) | |
| 58 | + return false; | |
| 59 | + return true; | |
| 60 | +} | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | +} | |
| 65 | +} | ... | ... |
include/CLI/TypeTools.hpp
0 → 100644
| 1 | +#pragma once | |
| 2 | + | |
| 3 | +// Distributed under the LGPL version 3.0 license. See accompanying | |
| 4 | +// file LICENSE or https://github.com/henryiii/CLI11 for details. | |
| 5 | + | |
| 6 | +#include <vector> | |
| 7 | +#include <type_traits> | |
| 8 | +#include <string> | |
| 9 | +#include <exception> | |
| 10 | + | |
| 11 | +namespace CLI { | |
| 12 | + | |
| 13 | +// Type tools | |
| 14 | + | |
| 15 | +// Copied from C++14 | |
| 16 | +#if __cplusplus < 201402L | |
| 17 | +template< bool B, class T = void > | |
| 18 | +using enable_if_t = typename std::enable_if<B,T>::type; | |
| 19 | +#else | |
| 20 | +// If your compiler supports C++14, you can use that definition instead | |
| 21 | +using std::enable_if_t; | |
| 22 | +#endif | |
| 23 | + | |
| 24 | +template <typename T> | |
| 25 | +struct is_vector { | |
| 26 | + static const bool value = false; | |
| 27 | +}; | |
| 28 | + | |
| 29 | + | |
| 30 | +template<class T, class A> | |
| 31 | +struct is_vector<std::vector<T, A> > { | |
| 32 | + static bool const value = true; | |
| 33 | +}; | |
| 34 | + | |
| 35 | +template <typename T> | |
| 36 | +struct is_bool { | |
| 37 | + static const bool value = false; | |
| 38 | +}; | |
| 39 | + | |
| 40 | +template<> | |
| 41 | +struct is_bool<bool> { | |
| 42 | + static bool const value = true; | |
| 43 | +}; | |
| 44 | + | |
| 45 | + | |
| 46 | +namespace detail { | |
| 47 | + // Based generally on https://rmf.io/cxx11/almost-static-if | |
| 48 | + /// Simple empty scoped class | |
| 49 | + enum class enabler {}; | |
| 50 | + | |
| 51 | + /// An instance to use in EnableIf | |
| 52 | + constexpr enabler dummy = {}; | |
| 53 | + | |
| 54 | + | |
| 55 | + // Type name print | |
| 56 | + | |
| 57 | + /// Was going to be based on | |
| 58 | + /// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template | |
| 59 | + /// But this is cleaner and works better in this case | |
| 60 | + | |
| 61 | + template<typename T, | |
| 62 | + enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy> | |
| 63 | + constexpr const char* type_name() { | |
| 64 | + return "INT"; | |
| 65 | + } | |
| 66 | + | |
| 67 | + template<typename T, | |
| 68 | + enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy> | |
| 69 | + constexpr const char* type_name() { | |
| 70 | + return "UINT"; | |
| 71 | + } | |
| 72 | + | |
| 73 | + | |
| 74 | + template<typename T, | |
| 75 | + enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> | |
| 76 | + constexpr const char* type_name() { | |
| 77 | + return "FLOAT"; | |
| 78 | + } | |
| 79 | + | |
| 80 | + | |
| 81 | + /// This one should not be used, since vector types print the internal type | |
| 82 | + template<typename T, | |
| 83 | + enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy> | |
| 84 | + constexpr const char* type_name() { | |
| 85 | + return "VECTOR"; | |
| 86 | + } | |
| 87 | + | |
| 88 | + | |
| 89 | + template<typename T, | |
| 90 | + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value | |
| 91 | + , detail::enabler> = detail::dummy> | |
| 92 | + constexpr const char* type_name() { | |
| 93 | + return "STRING"; | |
| 94 | + } | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + // Lexical cast | |
| 99 | + | |
| 100 | + | |
| 101 | + /// Integers | |
| 102 | + template<typename T, enable_if_t<std::is_integral<T>::value, detail::enabler> = detail::dummy> | |
| 103 | + bool lexical_cast(std::string input, T& output) { | |
| 104 | + try{ | |
| 105 | + output = (T) std::stoll(input); | |
| 106 | + return true; | |
| 107 | + } catch (std::invalid_argument) { | |
| 108 | + return false; | |
| 109 | + } catch (std::out_of_range) { | |
| 110 | + return false; | |
| 111 | + } | |
| 112 | + } | |
| 113 | + | |
| 114 | + /// Floats | |
| 115 | + template<typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> | |
| 116 | + bool lexical_cast(std::string input, T& output) { | |
| 117 | + try{ | |
| 118 | + output = (T) std::stold(input); | |
| 119 | + return true; | |
| 120 | + } catch (std::invalid_argument) { | |
| 121 | + return false; | |
| 122 | + } catch (std::out_of_range) { | |
| 123 | + return false; | |
| 124 | + } | |
| 125 | + } | |
| 126 | + | |
| 127 | + /// Vector | |
| 128 | + template<typename T, | |
| 129 | + enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy> | |
| 130 | + bool lexical_cast(std::string input, T& output) { | |
| 131 | + if(output.size() == input.size()) | |
| 132 | + output.resize(input.size()); | |
| 133 | + for(size_t i=0; i<input.size(); i++) | |
| 134 | + output[i] = input[i]; | |
| 135 | + return true; | |
| 136 | + } | |
| 137 | + | |
| 138 | + /// String and similar | |
| 139 | + template<typename T, | |
| 140 | + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value | |
| 141 | + , detail::enabler> = detail::dummy> | |
| 142 | + bool lexical_cast(std::string input, T& output) { | |
| 143 | + output = input; | |
| 144 | + return true; | |
| 145 | + } | |
| 146 | + | |
| 147 | + | |
| 148 | +} | |
| 149 | +} | ... | ... |
include/CLI/Value.hpp
0 → 100644
| 1 | +#pragma once | |
| 2 | + | |
| 3 | +// Distributed under the LGPL version 3.0 license. See accompanying | |
| 4 | +// file LICENSE or https://github.com/henryiii/CLI11 for details. | |
| 5 | + | |
| 6 | +#include <string> | |
| 7 | +#include <memory> | |
| 8 | + | |
| 9 | +#include "CLI/Error.hpp" | |
| 10 | + | |
| 11 | +namespace CLI { | |
| 12 | + | |
| 13 | +class App; | |
| 14 | + | |
| 15 | +// Prototype return value test | |
| 16 | +template <typename T> | |
| 17 | +class Value { | |
| 18 | + friend App; | |
| 19 | +protected: | |
| 20 | + std::shared_ptr<std::unique_ptr<T>> value {new std::unique_ptr<T>()}; | |
| 21 | + std::string name; | |
| 22 | +public: | |
| 23 | + Value(std::string name) : name(name) {} | |
| 24 | + | |
| 25 | + operator bool() const {return (bool) *value;} | |
| 26 | + | |
| 27 | + T& get() const { | |
| 28 | + if(*value) | |
| 29 | + return **value; | |
| 30 | + else | |
| 31 | + throw EmptyError(name); | |
| 32 | + } | |
| 33 | + /// Note this does not throw on assignment, though | |
| 34 | + /// afterwards it seems to work fine. Best to use | |
| 35 | + /// explicit * notation. | |
| 36 | + T& operator *() const { | |
| 37 | + return get(); | |
| 38 | + } | |
| 39 | +}; | |
| 40 | + | |
| 41 | +} | ... | ... |
scripts/MakeSingleHeader.py
0 → 100755
| 1 | +#!/usr/bin/env python | |
| 2 | + | |
| 3 | +# Requires Python 3.6 | |
| 4 | + | |
| 5 | +from plumbum import local, cli, FG | |
| 6 | +import re | |
| 7 | + | |
| 8 | +includes_local = re.compile(r"""^#include "(.*)"$""", re.MULTILINE) | |
| 9 | +includes_system = re.compile(r"""^#include \<(.*)\>$""", re.MULTILINE) | |
| 10 | + | |
| 11 | +DIR = local.path(__file__).dirname | |
| 12 | +BDIR = DIR / '../include' | |
| 13 | + | |
| 14 | +class MakeHeader(cli.Application): | |
| 15 | + | |
| 16 | + def main(self, out : cli.NonexistentPath = BDIR / 'CLI11.hpp'): | |
| 17 | + main_header = BDIR / 'CLI/CLI.hpp' | |
| 18 | + header = main_header.read() | |
| 19 | + | |
| 20 | + include_files = includes_local.findall(header) | |
| 21 | + | |
| 22 | + headers = set() | |
| 23 | + output = '' | |
| 24 | + with open('output.hpp', 'w') as f: | |
| 25 | + for inc in include_files: | |
| 26 | + inner = (BDIR / inc).read() | |
| 27 | + headers |= set(includes_system.findall(inner)) | |
| 28 | + output += f'\n// From {inc}\n\n' | |
| 29 | + output += inner[inner.find('namespace'):] | |
| 30 | + | |
| 31 | + header_list = '\n'.join(f'#include <{h}>' for h in headers) | |
| 32 | + | |
| 33 | + output = f'''\ | |
| 34 | +#pragma once | |
| 35 | + | |
| 36 | +// Distributed under the LGPL version 3.0 license. See accompanying | |
| 37 | +// file LICENSE or https://github.com/henryiii/CLI11 for details. | |
| 38 | + | |
| 39 | +// This file was generated using MakeSingleHeader.py in CLI11/scripts | |
| 40 | +// This has the complete CLI library in one file. | |
| 41 | + | |
| 42 | +{header_list} | |
| 43 | +{output}''' | |
| 44 | + | |
| 45 | + with out.open('w') as f: | |
| 46 | + f.write(output) | |
| 47 | + | |
| 48 | + print(f"Created {out}") | |
| 49 | + | |
| 50 | +if __name__ == '__main__': | |
| 51 | + MakeHeader() | |
| 52 | + | |
| 53 | + | ... | ... |
tests/CLITest.cpp