From 2db15139cb34e799aebaadf876f9d96035bc0da9 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Wed, 25 Jan 2017 17:28:39 -0500 Subject: [PATCH] Initial commit of CLI library. A few things working, but not much --- CLI.hpp | 465 ++++++++++++++++++++++++++++++++++++++++++++++++++ Program.hpp | 140 +++++++++++++++ ProgramOP.hpp | 174 +++++++++++++++++++ try.cpp | 20 +++ try1.cpp | 32 ++++ 5 files changed, 831 insertions(+) create mode 100644 CLI.hpp create mode 100644 Program.hpp create mode 100644 ProgramOP.hpp create mode 100644 try.cpp create mode 100644 try1.cpp diff --git a/CLI.hpp b/CLI.hpp new file mode 100644 index 00000000..689c5cd8 --- /dev/null +++ b/CLI.hpp @@ -0,0 +1,465 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// This is unreachable outside this file; you should not use Combiner directly +namespace { + +void logit(std::string output) { + std::cout << output << std::endl; +} + +template +std::string join(const T& v, std::string delim = ",") { + std::ostringstream s; + for (const auto& i : v) { + if (&i != &v[0]) { + s << delim; + } + s << i; + } + return s.str(); +} + + + + +struct Combiner { + int num; + bool positional; + bool required; + bool defaulted; + + /// Can be or-ed together + Combiner operator | (Combiner b) const { + Combiner self; + self.num = num + b.num; + self.positional = positional || b.positional; + self.required = required || b.required; + self.defaulted = defaulted || b.defaulted; + return self; + } + + /// Call to give the number of arguments expected on cli + Combiner operator() (int n) const { + return Combiner{n, positional, required, defaulted}; + } + Combiner operator, (Combiner b) const { + return *this | b; + } +}; + + +} + +namespace CLI { + +class BadNameString : public std::runtime_error { +public: + BadNameString(std::string name) : runtime_error("Failed to parse: " + name) {}; +}; + +class CallForHelp : public std::runtime_error { +public: + CallForHelp() : runtime_error("Help option passed") {}; +}; + +class ParseError : public std::runtime_error { +public: + ParseError(std::string info="") : runtime_error(info) {}; +}; + +class OptionAlreadyAdded : public std::runtime_error { +public: + OptionAlreadyAdded(std::string name) : runtime_error("Already added:" + name) {}; +}; + + + +const std::regex reg_split{R"regex((?:([a-zA-Z0-9]?)(?:,|$)|^)([a-zA-Z0-9][a-zA-Z0-9_\-]*)?)regex"}; +const std::regex reg_short{R"regex(-([^-])(.*))regex"}; +const std::regex reg_long{R"regex(--([^-^=][^=]*)=?(.*))regex"}; + + +std::tuple split(std::string fullname) throw(BadNameString) { + + std::smatch match; + if (std::regex_match(fullname, match, reg_split)) { + std::string sname = match[1]; + std::string lname = match[2]; + if(sname == "" and lname == "") + throw BadNameString("EMPTY"); + return std::tuple(sname, lname); + } else throw BadNameString(fullname); +} + +const Combiner NOTHING {0,false,false,false}; +const Combiner REQUIRED {0,false,true, false}; +const Combiner DEFAULT {0,false,false,true}; +const Combiner POSITIONAL{0,true, false,false}; +const Combiner ARGS {1,false,false,false}; + +typedef std::vector> results_t; +typedef std::function callback_t; + +class Option { +public: +protected: + // Config + std::string sname; + std::string lname; + Combiner opts; + std::string discription; + callback_t callback; + + // Results + results_t results {}; + + +public: + Option(std::string name, std::string discription = "", Combiner opts=NOTHING, std::function callback=[](results_t){return true;}) throw (BadNameString) : + opts(opts), discription(discription), callback(callback){ + std::tie(sname, lname) = split(name); + } + + /// Process the callback + bool run_callback() const { + return callback(results); + } + + /// Indistinguishible options are equal + bool operator== (const Option& other) const { + if(sname=="" && other.sname=="") + return lname==other.lname; + else if(lname=="" && other.lname=="") + return sname==other.sname; + else + return sname==other.sname || lname==other.lname; + } + + std::string getName() const { + if(sname=="") + return "--" + lname; + else if (lname=="") + return "-" + sname; + else + return "-" + sname + ", --" + lname; + } + + bool check_sname(const std::string& name) const { + return name == sname; + } + + bool check_lname(const std::string& name) const { + return name == lname; + } + + std::string get_sname() const { + return sname; + } + + std::string get_lname() const { + return lname; + } + + + int get_num() const { + return opts.num; + } + + void add_result(int r, std::string s) { + results.at(r).push_back(s); + } + int get_new() { + results.emplace_back(); + return results.size() - 1; + } + int count() { + return results.size(); + } + + std::string string() const { + std::string val = "Option: " + getName() + "\n" + + " " + discription + "\n" + + " ["; + for(const auto& item : results) { + if(&item!=&results[0]) + val+="],["; + val += join(item); + } + val += "]"; + return val; + } + +}; + +/// Creates a command line program, with very few defaults. +/** To use, create a new Program() instance with argc, argv, and a help discription. The templated +* add_option methods make it easy to prepare options. Remember to call `.start` before starting your +* program, so that the options can be evaluated and the help option doesn't accidentally run your program. */ +class App { +public: + +protected: + + std::string desc; + std::vector