JASSv2
commandline.h
Go to the documentation of this file.
1 /*
2  COMMANDLINE.H
3  -------------
4  Copyright (c) 2017 Andrew Trotman
5  Released under the 2-clause BSD license (See:https://en.wikipedia.org/wiki/BSD_licenses)
6 */
13 #pragma once
14 #include <tuple>
15 #include <limits>
16 #include <string>
17 #include <utility>
18 #include <sstream>
19 #include <iostream>
20 #include <typeinfo>
21 
22 #include <stdlib.h>
23 
24 #include "string.h"
25 #include "asserts.h"
26 
27 namespace JASS
28  {
29  /*
30  CLASS COMMANDLINE
31  -----------------
32  */
37  {
38  private:
39  /*
40  CLASS COMMANDLINE::TEXT_NOTE
41  ----------------------------
42  */
46  class text_note
47  {
48  /* Nothing */
49  };
50 
51  /*
52  CLASS COMMANDLINE::COMMAND
53  --------------------------
54  */
58  template <typename TYPE>
59  class command
60  {
61  public:
62  std::string shortname;
63  std::string longname;
64  std::string description;
65  TYPE &parameter;
66 
67  public:
68 
69  /*
70  CLASS COMMANDLINE::COMMAND::COMMAND
71  -----------------------------------
72  */
80  command(const std::string &shortname, const std::string &longname, const std::string &description, TYPE &parameter) :
81  shortname(shortname),
82  longname(longname),
83  description(description),
84  parameter(parameter)
85  {
86  /* Nothing */
87  }
88  };
89 
90  private:
91  /*
92  COMMANDLINE::EXTRACT
93  --------------------
94  */
102  static void extract(std::ostringstream &messages, const char *parameter, command<text_note> element)
103  {
104  /* Nothing */
105  }
106 
107  /*
108  COMMANDLINE::EXTRACT
109  --------------------
110  */
117  static void extract(std::ostringstream &messages, const char *parameter, command<bool> element)
118  {
119  element.parameter = true;
120  }
121 
122  /*
123  COMMANDLINE::EXTRACT
124  --------------------
125  */
133  template <typename TYPE,
134  typename std::enable_if <std::is_same<TYPE, short>::value ||
135  std::is_same<TYPE, int>::value ||
136  std::is_same<TYPE, long>::value ||
137  std::is_same<TYPE, long long>::value>::type* = nullptr>
138  static void extract(std::ostringstream &messages, const char *parameter, command<TYPE> element)
139  {
140  if (parameter == nullptr)
141  {
142  messages << " Missing numeric parameter\n";
143  return;
144  }
145 
146  long long answer = strtoll(parameter, NULL, 0);
147 
148  if (answer > (std::numeric_limits<TYPE>::max)())
149  messages << parameter << " Numeric overflow on parameter\n";
150  else if (answer < (std::numeric_limits<TYPE>::min)())
151  messages << parameter << " Numeric underflow on parameter\n";
152  else
153  element.parameter = static_cast<TYPE>(answer);
154  }
155 
156  /*
157  COMMANDLINE::EXTRACT
158  --------------------
159  */
167  template <typename TYPE,
168  typename std::enable_if <std::is_same<TYPE, unsigned short>::value ||
169  std::is_same<TYPE, unsigned int>::value ||
170  std::is_same<TYPE, unsigned long>::value ||
171  std::is_same<TYPE, unsigned long long>::value>::type* = nullptr>
172  static void extract(std::ostringstream &messages, const char *parameter, command<TYPE> element)
173  {
174  if (parameter == nullptr)
175  {
176  messages << " Missing numeric parameter\n";
177  return;
178  }
179 
180  unsigned long long answer = strtoull(parameter, NULL, 0);
181 
182  /*
183  Coverity Scan complains about the line below when TYPE is a unisgned long long because it can never be true.
184  */
185  if constexpr (!std::is_same<TYPE, unsigned long long>::value)
186  if (answer > (std::numeric_limits<TYPE>::max)())
187  messages << parameter << " Numeric overflow on parameter\n";
188 
189  element.parameter = static_cast<TYPE>(answer);
190  }
191 
192  /*
193  COMMANDLINE::EXTRACT
194  --------------------
195  */
203  template <typename TYPE,
204  typename std::enable_if <std::is_same<TYPE, float>::value ||
205  std::is_same<TYPE, double>::value>::type* = nullptr>
206  static void extract(std::ostringstream &messages, const char *parameter, command<TYPE> element)
207  {
208  double answer = std::stod(parameter);
209 
210  if (answer > (std::numeric_limits<TYPE>::max)())
211  messages << parameter << " Numeric overflow on parameter\n";
212  else if (answer < (std::numeric_limits<TYPE>::min)())
213  messages << parameter << " Numeric underflow on parameter\n";
214  else
215  element.parameter = static_cast<TYPE>(answer);
216  }
217 
218  /*
219  COMMANDLINE::EXTRACT
220  --------------------
221  */
228  static void extract(std::ostringstream &messages, const char *parameter, command<std::string> element)
229  {
230  element.parameter = parameter;
231  }
232 
233  /*
234  COMMANDLINE::EXTRACT
235  --------------------
236  */
243  template <typename TYPE>
244  static void extract(std::ostringstream &messages, const char *parameter, TYPE element)
245  {
246  messages << element.shortname << " (" << element.longname << ") Unknown parameter type\n";
247  }
248 
249  /*
250  COMMANDLINE::FOR_EACH_PARAMETER()
251  ---------------------------------
252  For information on interating over a tuple see here https://stackoverflow.com/questions/1198260/iterate-over-tuple
253  */
260  template<std::size_t I = 0, typename... Tp>
261  inline typename std::enable_if<I == sizeof...(Tp), void>::type
262  static for_each_parameter(std::ostringstream &messages, size_t &arg, const char *argv[], std::tuple<Tp...> &)
263  {
264  messages << argv[arg] << " Unknown parameter\n";
265  arg++;
266  }
267 
268  /*
269  COMMANDLINE::FOR_EACH_PARAMETER()
270  ---------------------------------
271  For information on interating over a tuple see here https://stackoverflow.com/questions/1198260/iterate-over-tuple
272  */
279  template<std::size_t I = 0, typename... Tp>
280  inline typename std::enable_if<I < sizeof...(Tp), void>::type
281  static for_each_parameter(std::ostringstream &messages, size_t &arg, const char *argv[], std::tuple<Tp...> &tuple)
282  {
283  if (std::get<I>(tuple).shortname.size() == 0 || std::get<I>(tuple).longname.size() == 0) // looking for non-parameters (i.e. notes_
284  for_each_parameter<I + 1, Tp...>(messages, arg, argv, tuple);
285  else if (strcmp(argv[arg], std::get<I>(tuple).shortname.c_str()) == 0) // looking for "-a v"
286  {
287  extract(messages, argv[++arg], std::get<I>(tuple));
288  if (typeid(std::get<I>(tuple).parameter) != typeid(bool))
289  arg++;
290  }
291  else if (strcmp(argv[arg], std::get<I>(tuple).longname.c_str()) == 0) // looking for "--aaa v"
292  {
293  extract(messages, argv[++arg], std::get<I>(tuple));
294  if (typeid(std::get<I>(tuple).parameter) != typeid(bool))
295  arg++;
296  }
297  else if (strncmp(argv[arg], std::get<I>(tuple).longname.c_str(), std::get<I>(tuple).longname.size()) == 0) // looking for "-aav"
298  {
299  extract(messages, argv[arg] + std::get<I>(tuple).longname.size(), std::get<I>(tuple));
300  arg++;
301  }
302  else if (strncmp(argv[arg], std::get<I>(tuple).shortname.c_str(), std::get<I>(tuple).shortname.size()) == 0) // looking for "-av"
303  {
304  extract(messages, argv[arg] + std::get<I>(tuple).shortname.size(), std::get<I>(tuple));
305  arg++;
306  }
307  else
308  for_each_parameter<I + 1, Tp...>(messages, arg, argv, tuple);
309  }
310 
311  /*
312  COMMANDLINE::FOR_EACH_USAGE_FORMATTING()
313  ----------------------------------------
314  For information on interating over a tuple see here https://stackoverflow.com/questions/1198260/iterate-over-tuple
315  */
324  template<std::size_t I = 0, typename... Tp>
325  inline typename std::enable_if<I == sizeof...(Tp), void>::type
326  static for_each_usage_formatting(size_t &width_of_shortname, size_t &width_of_longname, const std::tuple<Tp...> &)
327  {
328  /* Nothing */
329  }
330 
331  /*
332  COMMANDLINE::FOR_EACH_USAGE_FORMATTING()
333  ----------------------------------------
334  For information on interating over a tuple see here https://stackoverflow.com/questions/1198260/iterate-over-tuple
335  */
344  template<std::size_t I = 0, typename... Tp>
345  inline typename std::enable_if<I < sizeof...(Tp), void>::type
346  static for_each_usage_formatting(size_t &width_of_shortname, size_t &width_of_longname, const std::tuple<Tp...> &tuple)
347  {
348  if (std::get<I>(tuple).shortname.size() > width_of_shortname)
349  width_of_shortname = std::get<I>(tuple).shortname.size();
350 
351  if (std::get<I>(tuple).longname.size() > width_of_longname)
352  width_of_longname = std::get<I>(tuple).longname.size();
353 
354  for_each_usage_formatting<I + 1, Tp...>(width_of_shortname, width_of_longname, tuple);
355  }
356 
357  /*
358  COMMANDLINE::FOR_EACH_USAGE_PRINT()
359  -----------------------------------
360  For information on interating over a tuple see here https://stackoverflow.com/questions/1198260/iterate-over-tuple
361  */
368  template<std::size_t I = 0, typename... Tp>
369  inline typename std::enable_if<I == sizeof...(Tp), void>::type
370  static for_each_usage_print(std::ostream &out, size_t width_of_shortname, size_t width_of_longname, const std::tuple<Tp...> &)
371  {
372  /* Nothing */
373  }
374 
375  /*
376  COMMANDLINE::FOR_EACH_USAGE_PRINT()
377  -----------------------------------
378  For information on interating over a tuple see here https://stackoverflow.com/questions/1198260/iterate-over-tuple
379  */
387  template<std::size_t I = 0, typename... Tp>
388  inline typename std::enable_if<I < sizeof...(Tp), void>::type
389  static for_each_usage_print(std::ostream &out, size_t width_of_shortname, size_t width_of_longname, const std::tuple<Tp...> &tuple)
390  {
391  /*
392  Save the state of the stream (because we're going to manipulate it)
393  */
394  std::ios state(NULL);
395  state.copyfmt(out);
396  std::ios_base::fmtflags fmt(std::cout.flags());
397  /*
398  Dump out the usage
399  */
400  if (std::get<I>(tuple).shortname.size() == 0)
401  out << std::left << std::get<I>(tuple).description << '\n';
402  else
403  {
404  out.width(width_of_shortname + 1);
405  out << std::left << std::get<I>(tuple).shortname;
406  out.width(width_of_longname + 1);
407  out << std::get<I>(tuple).longname;
408  out << std::get<I>(tuple).description << '\n';
409  }
410 
411  /*
412  Return the state to how it was before we changed it
413  */
414  std::cout.flags(fmt);
415  out.copyfmt(state);
416 
417  for_each_usage_print<I + 1, Tp...>(out, width_of_shortname, width_of_longname, tuple);
418  }
419 
420  public:
421  /*
422  COMMANDLINE::PARAMETER()
423  ------------------------
424  */
440  template <typename TYPE>
441  static command<TYPE> parameter(const std::string &shortname, const std::string &longname, const std::string &description, TYPE &parameter)
442  {
443  return command<TYPE>(shortname, longname, description, parameter);
444  }
445 
446 
447  /*
448  COMMANDLINE::NOTE()
449  -------------------
450  */
462  static command<text_note> note(const std::string &description)
463  {
464  text_note blank;
465 
466  return command<text_note>("", "", description, blank);
467  }
468 
469  /*
470  COMMANDLINE::PARSE()
471  --------------------
472  */
481  template <typename... TYPE>
482  static bool parse(int argc, const char *argv[], std::tuple<TYPE...> &all_parameters, std::string &error)
483  {
484  std::ostringstream messages;
485  size_t argument = 1;
486 
487  while (argument < static_cast<size_t>(argc))
488  for_each_parameter(messages, argument, argv, all_parameters);
489 
490  error = messages.str();
491  return error == "";
492  }
493 
494  /*
495  COMMANDLINE::USAGE()
496  --------------------
497  */
503  template <typename... TYPE>
504  static std::string usage(const std::string &exename, const std::tuple<TYPE...> &all_parameters)
505  {
506  size_t width_of_shortname = 0;
507  size_t width_of_longname = 0;
508  std::ostringstream answer;
509 
510  /*
511  Compute the length of the shortname and longname fields.
512  */
513  for_each_usage_formatting(width_of_shortname, width_of_longname, all_parameters);
514 
515  /*
516  Print the usage instructions.
517  */
518  for_each_usage_print(answer, width_of_shortname, width_of_longname, all_parameters);
519 
520  return answer.str();
521  }
522 
523  /*
524  ALLOCATOR_CPP::UNITTEST()
525  -------------------------
526  */
530  static void unittest(void)
531  {
532  /*
533  Declare some objects
534  */
535  bool parameter_boolean = false;
536  std::string parameter_string = "Something";
537  int parameter_integer = 0;
538  unsigned int parameter_unsigned = 0;
539  unsigned long long parameter_unsigned_long_long;
540  long long parameter_long_long;
541 
542  /*
543  Declare the parameter object to parse
544  */
545  auto all_commands = std::make_tuple
546  (
547  commandline::note("PARAMETERS"),
548  commandline::parameter("-b", "--boolean", "Extract a boolean", parameter_boolean),
549  commandline::parameter("-s", "--string", "Extractr a string", parameter_string),
550  commandline::parameter("-i", "--integer", "Extract an integer", parameter_integer),
551  commandline::parameter("-u", "--unsigned", "Extract an unsigned integer", parameter_unsigned),
552  commandline::parameter("-h", "--huge", "Extract an unsigned ilong long nteger", parameter_unsigned_long_long),
553  commandline::parameter("-H", "--Huge", "Extract an long log integer", parameter_long_long)
554  );
555 
556  /*
557  Declare a command line like argc and argv[].
558  */
559  int argc = 9;
560  const char *argv[] = {"program", "-b", "-s", "string", "-i3", "-u", "4", "-h0", "-H0"};
561 
562  /*
563  Call the parser
564  */
565  std::string error;
566  auto success = commandline::parse(argc, argv, all_commands, error);
567 
568  /*
569  Make sure we got the right answer.
570  */
571  JASS_assert(success);
572  JASS_assert(error == "");
573  std::ostringstream results;
574  results << parameter_boolean << parameter_string << parameter_integer << parameter_unsigned << parameter_unsigned_long_long;
575  JASS_assert(results.str() == "1string340");
576 
577  /*
578  Check longnames
579  */
580  parameter_boolean = false;
581  argc = 7;
582  const char *argv2[] = {"program", "--boolean", "--string", "four", "--integer5", "--unsigned", "6"};
583  success = commandline::parse(argc, argv2, all_commands, error);
584  JASS_assert(success);
585  std::ostringstream results2;
586  results2 << parameter_boolean << parameter_string << parameter_integer << parameter_unsigned;
587  JASS_assert(results2.str() == "1four56");
588 
589  /*
590  check for errors
591  */
592  class sptang
593  {
594  /* Nothing */
595  } funny_object;
596  auto error_commands = std::make_tuple
597  (
598  commandline::note("PARAMETERS"),
599  commandline::parameter("-b", "--boolean", "Extract a boolean", parameter_boolean),
600  commandline::parameter("-s", "--string", "Extractr a string", parameter_string),
601  commandline::parameter("-i", "--integer", "Extract an integer", parameter_integer),
602  commandline::parameter("-u", "--unsigned", "Extract an unsigned integer", parameter_unsigned),
603  commandline::parameter("-f", "--funny", "Extract an object", funny_object)
604  );
605 
606  const char *argv3[] = {"program", "--integer", "2147483648", "--unsigned", "4294967296", "--integer", "-2147483649", "--nonexistant", "--funny"};
607  success = commandline::parse(9, argv3, error_commands, error);
608  JASS_assert(!success);
609  std::string answer =
610  "2147483648 Numeric overflow on parameter\n"
611  "4294967296 Numeric overflow on parameter\n"
612  "-2147483649 Numeric underflow on parameter\n"
613  "--nonexistant Unknown parameter\n"
614  "-f (--funny) Unknown parameter type\n";
615  JASS_assert(error == answer);
616 
617  auto how_to = usage("exename", error_commands);
618  std::string how_to_use =
619  "PARAMETERS\n"
620  "-b --boolean Extract a boolean\n"
621  "-s --string Extractr a string\n"
622  "-i --integer Extract an integer\n"
623  "-u --unsigned Extract an unsigned integer\n"
624  "-f --funny Extract an object\n";
625  JASS_assert(how_to == how_to_use);
626 
627  puts("commandline:PASSED");
628  }
629  };
630  }
static void extract(std::ostringstream &messages, const char *parameter, command< text_note > element)
Dummy for use with text embedded in the parameter lists.
Definition: commandline.h:102
A single command line parameter.
Definition: commandline.h:59
replacement for the C runtime library assert that also works in release.
static void extract(std::ostringstream &messages, const char *parameter, TYPE element)
Catch all for unknown types.
Definition: commandline.h:244
used to specify text strings to appear in the help.
Definition: commandline.h:46
static void extract(std::ostringstream &messages, const char *parameter, command< TYPE > element)
Extract an integer value of the parameter from the command line parameters.
Definition: commandline.h:138
#define JASS_assert(expression)
Drop in replacement for assert() that aborts in Release as well as Debug.
Definition: asserts.h:33
static void extract(std::ostringstream &messages, const char *parameter, command< bool > element)
Extract a boolean value of the parameter from the command line parameters.
Definition: commandline.h:117
static void extract(std::ostringstream &messages, const char *parameter, command< std::string > element)
Extract a string value of the parameter from the command line parameters.
Definition: commandline.h:228
Command line processor.
Definition: commandline.h:36
std::enable_if< I< sizeof...(Tp), void >::type static for_each_parameter(std::ostringstream &messages, size_t &arg, const char *argv[], std::tuple< Tp... > &tuple) { if(std::get< I >tuple).shortname.size()==0||std::get< I >tuple).longname.size()==0) for_each_parameter< I+1, Tp... >messages, arg, argv, tuple);else if(strcmp(argv[arg], std::get< I >tuple).shortname.c_str())==0) { extract(messages, argv[++arg], std::get< I >tuple));if(typeid(std::get< I >tuple).parameter) !=typeid(bool)) arg++;} else if(strcmp(argv[arg], std::get< I >tuple).longname.c_str())==0) { extract(messages, argv[++arg], std::get< I >tuple));if(typeid(std::get< I >tuple).parameter) !=typeid(bool)) arg++;} else if(strncmp(argv[arg], std::get< I >tuple).longname.c_str(), std::get< I >tuple).longname.size())==0) { extract(messages, argv[arg]+std::get< I >tuple).longname.size(), std::get< I >tuple));arg++;} else if(strncmp(argv[arg], std::get< I >tuple).shortname.c_str(), std::get< I >tuple).shortname.size())==0) { extract(messages, argv[arg]+std::get< I >tuple).shortname.size(), std::get< I >tuple));arg++;} else for_each_parameter< I+1, Tp... >messages, arg, argv, tuple);} template< std::size_t I=0, typename... Tp > inline typename std::enable_if< I==sizeof...(Tp), void >::type static for_each_usage_formatting(size_t &width_of_shortname, size_t &width_of_longname, const std::tuple< Tp... > &) { } template< std::size_t I=0, typename... Tp > inline typename std::enable_if< I< sizeof...(Tp), void >::type static for_each_usage_formatting(size_t &width_of_shortname, size_t &width_of_longname, const std::tuple< Tp... > &tuple) { if(std::get< I >tuple).shortname.size() > width_of_shortname) width_of_shortname=std::get< I >tuple).shortname.size();if(std::get< I >tuple).longname.size() > width_of_longname width_of_longname
Iterate over each parameter looking for one that matches.
Definition: commandline.h:352
static std::enable_if< I==sizeof...(Tp), void >::type for_each_usage_print(std::ostream &out, size_t width_of_shortname, size_t width_of_longname, const std::tuple< Tp... > &)
Iterate over each parameter printing the shortname, longname, and description.
Definition: commandline.h:370
command(const std::string &shortname, const std::string &longname, const std::string &description, TYPE &parameter)
Build an object that represents a possible command line parameter.
Definition: commandline.h:80
TYPE & parameter
A reference to the external variable to set based on the command line.
Definition: commandline.h:65
static std::enable_if< I==sizeof...(Tp), void >::type for_each_parameter(std::ostringstream &messages, size_t &arg, const char *argv[], std::tuple< Tp... > &)
Iterate over each parameter looking for one that matches.
Definition: commandline.h:262
std::string shortname
The short name to match e.g. "-m".
Definition: commandline.h:62
Definition: compress_integer_elias_delta_simd.c:23
std::string longname
The long name to mathc e.g. "-mood".
Definition: commandline.h:63
std::string description
The descriptrion to show when the user asks for help e.g. "The users&#39; mood".
Definition: commandline.h:64