13 #ifndef HELIB_ARGMAP_H 
   14 #define HELIB_ARGMAP_H 
   18 #include <forward_list> 
   19 #include <initializer_list> 
   21 #include <unordered_map> 
   31 #include <type_traits> 
   33 #include <helib/assertions.h> 
   85   class PositionalArgsList
 
   88     std::vector<std::string> positional_args;
 
   89     bool optional_flag = 
false;
 
   92     void insert(std::string name, 
bool optional)
 
   95         this->optional_flag = 
true;
 
   96         positional_args.push_back(name);
 
   97       } 
else if (!
optional && !optional_flag) {
 
   98         positional_args.push_back(name);
 
  100         throw LogicError(
"Attempting to have argument '" + name +
 
  101                          "' required after optional positional args given.");
 
  105     std::vector<std::string>::iterator begin()
 
  107       return this->positional_args.begin();
 
  110     std::vector<std::string>::iterator end()
 
  112       return this->positional_args.end();
 
  115     bool empty() { 
return this->positional_args.empty(); }
 
  120   template <
typename C>
 
  121   class ArgProcessorContainer;
 
  124   template <
typename T>
 
  125   class ArgProcessorValue;
 
  128   ArgType arg_type = ArgType::NAMED;
 
  129   char kv_separator = 
'=';
 
  131   bool required_mode = 
false;
 
  132   bool dots_enabled = 
false;
 
  133   bool named_args_only = 
true;
 
  135   std::string progname;
 
  136   std::string dots_name;
 
  139   std::set<void*> addresses_used;
 
  142   std::set<std::string> previous_call_set;
 
  145   std::unordered_map<std::string, std::shared_ptr<ArgProcessor>> map;
 
  149   std::vector<std::tuple<std::string, std::string, bool>> docVec;
 
  151   PositionalArgsList positional_args_list;
 
  153   std::unique_ptr<ArgProcessor> dots_ap;
 
  156   std::set<std::string> required_set;
 
  159   std::set<std::string> optional_set;
 
  161   std::ostream* diagnostics_strm = 
nullptr;
 
  163   std::set<std::string> help_tokens = {
"-h", 
"--help"};
 
  166   void printDiagnostics(
const std::forward_list<std::string>& args) 
const;
 
  175   void simpleParse(
const std::forward_list<std::string>& args,
 
  176                    bool duplicates = 
true,
 
  177                    std::function<
void(
const std::string&)> stop = {});
 
  198   template <
typename T>
 
  199   ArgMap& 
arg(
const std::string& name, T& value);
 
  213   template <
typename T>
 
  214   ArgMap& 
arg(
const std::string& name, T& value, 
const std::string& 
doc);
 
  229   template <
typename T>
 
  232               const std::string& 
doc,
 
  242   template <
typename C>
 
  340   void usage(
const std::string& msg = 
"") 
const;
 
  347   std::string 
doc() 
const;
 
  351 static inline void lstrip(std::string& s)
 
  353   auto it = std::find_if(s.begin(), s.end(), [](
unsigned char c) {
 
  354     return !std::isspace(c);
 
  357   s.erase(s.begin(), it);
 
  360 static inline void rstrip(std::string& s)
 
  362   auto it = std::find_if(s.rbegin(), s.rend(), [](
unsigned char c) {
 
  363     return !std::isspace(c);
 
  366   s.erase(it.base(), s.end());
 
  369 static inline void strip(std::string& s)
 
  376 static void splitOnSeparator(std::forward_list<std::string>& args_lst, 
char sep)
 
  381   for (
auto it = args_lst.begin(); it != args_lst.end(); ++it) {
 
  382     if (it->size() != 1) {
 
  383       std::size_t pos = it->find(sep);
 
  384       if (pos != std::string::npos) {
 
  386           std::string 
sub = it->substr(1, std::string::npos);
 
  388           args_lst.insert_after(it, 
sub);
 
  390           std::string 
sub = it->substr(pos);
 
  391           *it = it->substr(0, pos);
 
  392           args_lst.insert_after(it, 
sub);
 
  406   virtual bool process(
const std::string& s) = 0;
 
  409 template <
typename C>
 
  410 class ArgMap::ArgProcessorContainer : 
public ArgProcessor 
  414   ArgType arg_type = ArgType::DOTS;
 
  416   using T = 
typename C::value_type;
 
  419   template <
typename U = T,
 
  421             typename std::enable_if_t<std::is_same<U, S>::value, 
int> = 0>
 
  422   bool do_process(
const S& s)
 
  424     container->push_back(s);
 
  429   template <
typename U = T,
 
  431             typename std::enable_if_t<!std::is_same<U, S>::value, 
int> = 0>
 
  432   bool do_process(
const S& s)
 
  434     std::istringstream iss(s);
 
  436     bool rt = (iss >> tmp_value);
 
  437     container->push_back(tmp_value);
 
  442   ArgType getArgType()
 override { 
return arg_type; }
 
  444   bool process(
const std::string& s)
 override { 
return this->do_process(s); }
 
  446   explicit ArgProcessorContainer(C* c) : container(c) {}
 
  450 template <
typename T>
 
  451 class ArgMap::ArgProcessorValue : 
public ArgProcessor
 
  458   template <
typename U = T,
 
  460             typename std::enable_if_t<std::is_same<U, S>::value, 
int> = 0>
 
  461   bool do_process(
const S& s)
 
  468   template <
typename U = T,
 
  470             typename std::enable_if_t<!std::is_same<U, S>::value, 
int> = 0>
 
  471   bool do_process(
const S& s)
 
  473     std::istringstream iss(s);
 
  474     return bool(iss >> *value);
 
  478   ArgType getArgType()
 override { 
return arg_type; }
 
  480   bool process(
const std::string& s)
 override { 
return this->do_process(s); }
 
  482   explicit ArgProcessorValue(T* v, ArgType at) : value(v), arg_type(at) {}
 
  485 template <
typename T>
 
  489   assertTrue<LogicError>(
 
  491           std::none_of(name.begin(),
 
  493                        [](
unsigned char c) { return std::isspace(c); }),
 
  494       "Attempting to register an empty string or string with whitespace");
 
  497   assertTrue<LogicError>(map.count(name) == 0,
 
  498                          "Key already in arg map (key: " + name + 
")");
 
  501   assertEq<LogicError>(addresses_used.count(&value),
 
  503                        "Attempting to register variable twice");
 
  505   addresses_used.insert(&value);
 
  507   map[name] = std::make_shared<ArgProcessorValue<T>>(&value, this->arg_type);
 
  509   if (this->arg_type == ArgType::POSITIONAL) {
 
  510     this->positional_args_list.insert(name, !this->required_mode);
 
  513   if (this->required_mode) {
 
  515     if (this->arg_type == ArgType::TOGGLE_TRUE ||
 
  516         this->arg_type == ArgType::TOGGLE_FALSE)
 
  517       throw LogicError(
"Toggle argument types cannot be required.");
 
  518     this->required_set.insert(name);
 
  521     this->optional_set.insert(name);
 
  527 template <
typename T>
 
  531   std::stringstream ss;
 
  532   ss << 
doc << 
" [ default=" << value << 
" ]";
 
  533   docVec.push_back({name, ss.str(), required_mode});
 
  538 template <
typename T>
 
  541                     const std::string& doc,
 
  546   docVec.push_back({name, 
doc, required_mode});
 
  547   if (info != 
nullptr && info[0] != 
'\0')
 
  548     std::get<1>(docVec.back()).append(
" [ default=").append(info).append(
" ]");
 
  553 template <
typename C>
 
  556   if (this->dots_enabled)
 
  557     throw LogicError(
".dots() can only be called once.");
 
  559   this->dots_enabled = 
true;
 
  560   this->dots_name = name;
 
  564   this->dots_ap = std::make_unique<ArgProcessorContainer<C>>(&container);
 
  571   std::get<1>(docVec.back()).append(
"\t\t").append(s);
 
  578     std::cerr << msg << 
'\n';
 
  580   decltype(docVec) docVecCopy = docVec;
 
  581   std::stable_partition(
 
  584       [
this](
const auto& item) {
 
  585         const auto& it = map.find(std::get<0>(item));
 
  587           throw LogicError(
"Not found in map '" + std::get<0>(item) + 
"'.");
 
  589           return it->second->getArgType() != ArgType::POSITIONAL;
 
  592   std::cerr << 
"Usage: " << this->progname;
 
  593   for (
const auto& 
doc : docVecCopy) {
 
  594     std::string name_ext(std::get<0>(
doc));
 
  595     auto it = map.find(name_ext);
 
  597       throw LogicError(
"Not found in map '" + name_ext + 
"'.");
 
  598     if (it->second->getArgType() == ArgType::NAMED)
 
  599       name_ext.append(1, this->kv_separator).append(
"<arg>");
 
  600     if (std::get<2>(
doc)) {
 
  601       std::cerr << 
" " << name_ext;
 
  603       std::cerr << 
" [" << name_ext << 
"]";
 
  607   if (this->dots_enabled)
 
  608     std::cerr << 
" [" << this->dots_name << 
" ...]\n";
 
  612   std::cerr << 
doc() << std::endl;
 
  619   this->help_tokens = s;
 
  625   this->help_tokens = {s};
 
  633     this->kv_separator = 
'=';
 
  636     this->kv_separator = 
':';
 
  639     this->kv_separator = 
' ';
 
  643     throw LogicError(
"Unrecognised option for kv separator.");
 
  651   this->required_mode = 
false;
 
  657   this->required_mode = 
true;
 
  663   this->named_args_only = 
false;
 
  664   this->arg_type = t ? ArgType::TOGGLE_TRUE : ArgType::TOGGLE_FALSE;
 
  670   this->arg_type = ArgType::NAMED;
 
  676   this->named_args_only = 
false;
 
  677   this->arg_type = ArgType::POSITIONAL;
 
  683   this->diagnostics_strm = &ostrm;
 
  687 inline void ArgMap::printDiagnostics(
 
  688     const std::forward_list<std::string>& args)
 const 
  690   if (this->diagnostics_strm != 
nullptr) {
 
  692     *this->diagnostics_strm << 
"Args pre-parse:\n";
 
  693     for (
const auto& e : args) {
 
  694       *this->diagnostics_strm << e << std::endl;
 
  697     *this->diagnostics_strm << 
"Required args set:\n";
 
  698     for (
const auto& e : required_set) {
 
  699       *this->diagnostics_strm << e << std::endl;
 
  702     *this->diagnostics_strm << 
"Optional args set:\n";
 
  703     for (
const auto& e : optional_set) {
 
  704       *this->diagnostics_strm << e << std::endl;
 
  711   std::stringstream ss;
 
  713       std::max_element(docVec.begin(),
 
  715                        [](
const auto& x, 
const auto& y) {
 
  716                          return std::get<0>(x).size() < std::get<0>(y).size();
 
  719   for (
const auto& p : docVec) {
 
  720     ss << 
"  " << std::left << std::setw(std::get<0>(*maxSzElem).length() + 1)
 
  721        << std::get<0>(p) << std::setw(0) << std::get<1>(p) << 
'\n';
 
  727 inline void ArgMap::simpleParse(
const std::forward_list<std::string>& args,
 
  729                                 std::function<
void(
const std::string&)> stop)
 
  731   if (stop == 
nullptr) {
 
  732     stop = std::bind(&
ArgMap::usage, 
this, std::placeholders::_1);
 
  735   auto pos_args_it = this->positional_args_list.begin();
 
  736   for (
auto it = args.begin(); it != args.end(); ++it) {
 
  738     const std::string token = *it;
 
  740     if (this->help_tokens.count(token))
 
  744     if (!duplicates && this->previous_call_set.count(token))
 
  745       stop(
"Attempting to set same variable '" + token + 
"' twice.");
 
  748     auto map_it = this->map.find(token);
 
  749     std::shared_ptr<ArgProcessor> ap =
 
  750         (map_it == this->map.end()) ? 
nullptr : map_it->second;
 
  752     if (ap && ap->getArgType() != ArgType::POSITIONAL) {
 
  754       switch (ap->getArgType()) {
 
  757         if ((++it) == args.end())
 
  758           stop(
"Dangling value for named argument '" + token + 
"'.");
 
  760         if (this->kv_separator == 
' ') {
 
  761           if (!ap->process(*it))
 
  762             stop(
"Whitespace separator issue. Value:'" + *it + 
"'");
 
  764           if ((++it) == 
args.end())
 
  765             stop(
"Dangling value for named argument '" + token +
 
  766                  "' after separator.");
 
  767           if (!ap->process(*it))
 
  768             stop(
"Not a valid value '" + *it + 
"'.");
 
  771       case ArgType::TOGGLE_TRUE:
 
  772         if (!ap->process(
"1"))
 
  775       case ArgType::TOGGLE_FALSE:
 
  776         if (!ap->process(
"0"))
 
  781         throw LogicError(
"Unrecognised ArgType.");
 
  786       this->required_set.erase(token);
 
  789       this->previous_call_set.insert(token);
 
  790     } 
else if (pos_args_it != this->positional_args_list.end()) {
 
  793       std::shared_ptr<ArgProcessor> pos_ap = map.find(*pos_args_it)->second;
 
  794       if (!pos_ap->process(*it))
 
  795         throw LogicError(
"Positional name does not match a ArgMap name.");
 
  797       this->required_set.erase(*pos_args_it);
 
  799     } 
else if (this->dots_enabled) {
 
  800       this->dots_ap->process(token);
 
  802       std::string msg = 
"Unrecognised argument \'" + token + 
"\'";
 
  803       if (!this->positional_args_list.empty())
 
  804         msg += 
"\nThere could be too many positional arguments";
 
  812   this->progname = std::string(argv[0]);
 
  814   std::forward_list<std::string> args(argv + 1, argv + argc);
 
  816   splitOnSeparator(args, this->kv_separator);
 
  819   std::for_each(args.begin(), args.end(), strip);
 
  821   printDiagnostics(args);
 
  826   if (!this->required_set.empty()) {
 
  827     std::ostringstream oss;
 
  828     oss << 
"Required argument(s) not given:\n";
 
  829     for (
const auto& e : this->required_set)
 
  830       oss << 
"\t" << e << 
'\n';
 
  840   if (this->kv_separator == 
' ') { 
 
  841     throw LogicError(
"Whitespace separator not possible from files.");
 
  844   if (!this->named_args_only) {
 
  845     throw LogicError(
"Toggle and Positional arguments not possible from " 
  846                      "files. Only named arguments.");
 
  849   std::ifstream file(filepath);
 
  850   this->progname = filepath;
 
  852   if (!file.is_open()) {
 
  853     throw RuntimeError(
"Could not open file '" + filepath + 
"'.");
 
  856   std::forward_list<std::string> args;
 
  857   auto it = args.before_begin();
 
  858   std::regex re_comment_lines(R
"((^\s*\#)|(^\s+$))"); 
  860   while (getline(file, line)) {
 
  861     if (line.empty() || std::regex_search(line, re_comment_lines)) {
 
  864     it = args.insert_after(it, line);
 
  867   splitOnSeparator(args, this->kv_separator);
 
  870   std::for_each(args.begin(), args.end(), strip);
 
  872   printDiagnostics(args);
 
  874   simpleParse(args, 
false, [&filepath](
const std::string& msg) {
 
  875     throw RuntimeError(
"Could not parse params file: '" + filepath + 
"'. " +
 
  880   if (!this->required_set.empty()) {
 
  881     std::ostringstream oss;
 
  882     oss << 
"Required argument(s) not given:\n";
 
  883     for (
const auto& e : this->required_set)
 
  884       oss << 
"\t" << e << 
'\n';
 
  893 #endif // ifndef HELIB_ARGMAP_H