All Classes Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
option_parser.hpp
1 // The MIT License (MIT)
2 
3 // Copyright (c) 2012-2014 Danny Y., Rapptz
4 
5 // Permission is hereby granted, free of charge, to any person obtaining a copy of
6 // this software and associated documentation files (the "Software"), to deal in
7 // the Software without restriction, including without limitation the rights to
8 // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 // the Software, and to permit persons to whom the Software is furnished to do so,
10 // subject to the following conditions:
11 
12 // The above copyright notice and this permission notice shall be included in all
13 // copies or substantial portions of the Software.
14 
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 
22 #ifndef GEARS_OPTPARSE_OPTION_PARSER_HPP
23 #define GEARS_OPTPARSE_OPTION_PARSER_HPP
24 
25 #include "error.hpp"
26 #include "formatter.hpp"
27 #include <iostream>
28 #include <cstdlib>
29 
30 namespace gears {
31 namespace optparse {
40 struct option_parser {
41 private:
42  std::vector<subcommand> subcommands;
43  option_set options;
44  std::unique_ptr<formatter> format = utility::make_unique<formatter>();
45  option_set* active_options = &options;
46  std::ptrdiff_t index = -1;
47 
48  bool is_option(const std::string& arg) const noexcept {
49  return arg.size() >= 2 && arg.front() == '-';
50  }
51 
52  template<typename ForwardIt>
53  ForwardIt process_subcommand(ForwardIt begin, ForwardIt end) {
54  if(begin == end) {
55  return begin;
56  }
57 
58  std::string arg = *begin;
59  if(arg.empty()) {
60  return begin;
61  }
62 
63  auto&& it = std::find_if(subcommands.begin(), subcommands.end(), [&arg](const subcommand& sub) {
64  return sub.name == arg;
65  });
66 
67  if(it != subcommands.end()) {
68  index = std::distance(subcommands.begin(), it);
69  active_options = &(it->options);
70  ++begin;
71  return begin;
72  }
73 
74  return begin;
75  }
76 
77  template<typename ForwardIt>
78  ForwardIt parse_long_option(std::string key, ForwardIt begin, ForwardIt end) {
79  std::string value;
80  auto&& pos = key.find('=');
81  bool has_explicit_value = false;
82 
83  // check if it's --long=arg
84  if(pos != std::string::npos) {
85  value = key.substr(pos + 1);
86  key = key.substr(0, pos);
87  has_explicit_value = true;
88  }
89 
90  // check if the argument exists
91  auto&& it = std::find_if(active_options->begin(), active_options->end(), [&key](const option& opt) {
92  return opt.is(key.substr(2));
93  });
94 
95  if(it == active_options->end()) {
97  }
98 
99  auto&& opt = *it;
100  size_t argc = std::distance(begin, end);
101 
102  // option doesn't take a value but it was explicitly provided
103  // so throw an error
104  if(!opt.takes_value() && has_explicit_value) {
106  }
107 
108  if(opt.takes_value() && !has_explicit_value) {
109  // check number of arguments left
110  if(argc - 1 < opt.nargs()) {
111  throw missing_required_value(program_name, key, opt.nargs());
112  }
113 
114  // get the arguments needed
115  for(size_t i = 0; i < opt.nargs(); ++i) {
116  value += *(++begin);
117  value.push_back('\n');
118  }
119 
120  // remove extraneous newline
121  if(!value.empty() && value.back() == '\n') {
122  value.pop_back();
123  }
124  }
125 
126  // parse it
127  if(opt.ptr != nullptr) {
128  opt.ptr->parse(key, value);
129  }
130 
131  return begin;
132  }
133 
134  template<typename ForwardIt>
135  ForwardIt parse_short_option(const std::string& arg, ForwardIt begin, ForwardIt end) {
136  std::string value;
137  // loop due to concatenation of short options
138  for(size_t j = 1 ; j < arg.size(); ++j) {
139  auto&& ch = arg[j];
140  bool has_explicit_value = false;
141  std::string key = "-";
142  key.push_back(ch);
143 
144  // check for -o=stuff
145  if(j + 2 < arg.size() && arg[j + 1] == '=') {
146  value = arg.substr(j + 2);
147  // set loop counter to end explicitly after
148  // this loop
149  j = arg.size();
150  has_explicit_value = true;
151  }
152 
153  auto&& it = std::find_if(active_options->begin(), active_options->end(), [&ch](const option& opt) {
154  return opt.is(ch);
155  });
156 
157  if(it == active_options->end()) {
158  throw unrecognised_option(program_name, key);
159  }
160 
161  auto&& opt = *it;
162  size_t argc = std::distance(begin, end);
163 
164  if(has_explicit_value && !opt.takes_value()) {
166  }
167 
168  // check for -o stuff
169  if(opt.takes_value() && !has_explicit_value) {
170  // note that -ostuff doesn't work due to ambiguity.
171  if(j + 1 != arg.size()) {
172  throw optparse::error(program_name, "short option \'" + key + "\' and value must not be combined", arg);
173  }
174 
175  if(argc - 1 < opt.nargs()) {
176  throw missing_required_value(program_name, key, opt.nargs());
177  }
178 
179  for(size_t i = 0; i < opt.nargs(); ++i) {
180  value += *(++begin);
181  value.push_back('\n');
182  }
183 
184  // remove extraneous newline
185  if(!value.empty() && value.back() == '\n') {
186  value.pop_back();
187  }
188  }
189 
190  if(opt.ptr != nullptr) {
191  opt.ptr->parse(key, value);
192  }
193  }
194 
195  return begin;
196  }
197 
198  template<typename It>
199  arguments make_args(It begin, It end) const {
200  if(index == -1) {
201  return { *active_options, std::vector<std::string>(begin, end), "" };
202  }
203  auto&& sub = subcommands[index];
204  return { *active_options, std::vector<std::string>(begin, end), sub.name };
205  }
206 public:
207  std::string description;
208  std::string epilogue;
209  std::string program_name;
210  std::string usage = "[options...]";
211 
215  option_parser() = default;
216 
220  option_parser(option_set options): options(std::move(options)) {}
221 
225  option_parser(std::initializer_list<option> options): options(std::move(options)) {}
226 
236  template<typename... Args>
237  void add(Args&&... args) {
238  options.add(std::forward<Args>(args)...);
239  }
240 
249  subcommands.push_back(std::move(sub));
250  return *this;
251  }
252 
260  template<typename Formatter>
261  void help_formatter(const Formatter& form) {
262  static_assert(std::is_base_of<formatter, Formatter>::value, "Must derive from formatter");
263  format = utility::make_unique<Formatter>(form);
264  }
265 
284  template<typename ForwardIt>
285  arguments raw_parse(ForwardIt begin, ForwardIt end) {
286  static_assert(std::is_constructible<std::string, decltype(*begin)>{},
287  "Iterator must return type convertible to std::string");
288 
289  if(begin == end) {
290  return {*active_options, {}, ""};
291  }
292 
293  // assign program name to argv[0] if it isn't provided
294  if(program_name.empty()) {
295  program_name = *begin++;
296  }
297  else {
298  ++begin;
299  }
300 
301  // check if argv[1] is a subcommand
302  begin = process_subcommand(begin, end);
303 
304  // begin parsing command line arguments
305  for(; begin != end; ++begin) {
306  std::string arg = *begin;
307 
308  // -- is used to delimit options vs positional arguments
309  if(arg == "--") {
310  return make_args(++begin, end);
311  }
312 
313  // check if it's an option
314  if(is_option(arg)) {
315  // check for long, i.e. --long
316  if(arg[1] == '-') {
317  begin = parse_long_option(arg, begin, end);
318  }
319  else {
320  // short option, i.e. -s
321  begin = parse_short_option(arg, begin, end);
322  }
323  }
324  else {
325  // since it isn't an option, then the positional
326  // arguments have begun so just stop parsing.
327  break;
328  }
329  }
330 
331  return make_args(begin, end);
332  }
333 
347  void notify() {
348  for(auto&& opt : *active_options) {
349  if((opt.flags & trait::required) == trait::required && !opt.is_active()) {
350  throw missing_required_option(program_name, opt.name.empty() ? std::string("-").append(1, opt.alias)
351  : "--" + opt.name);
352  }
353  }
354  }
355 
376  template<typename ForwardIt>
377  arguments parse(ForwardIt begin, ForwardIt end, std::ostream& out = std::cout, std::ostream& err = std::cerr) {
378  try {
379  auto&& args = raw_parse(begin, end);
380 
381  if(args.options.is_active("help")) {
382  out << format_help();
383  std::exit(EXIT_SUCCESS);
384  }
385 
386  notify();
387 
388  return args;
389  }
390  catch(const optparse::error& e) {
391  err << format_usage() << e.what() << '\n';
392  }
393  catch(const std::exception& e) {
394  err << format_usage() << program_name << ": error: " << e.what() << '\n';
395  }
396 
397  if(active_options->is_active("help")) {
398  out << format_description()
399  << format_subcommands()
400  << format_options()
401  << format_epilogue();
402  }
403 
404  std::exit(EXIT_FAILURE);
405  }
406 
413  std::string format_description() const noexcept {
414  return format->description(index == -1 ? epilogue : subcommands[index].epilogue);
415  }
416 
423  std::string format_epilogue() const noexcept {
424  return format->epilogue(index == -1 ? epilogue : subcommands[index].epilogue);
425  }
426 
433  std::string format_usage() const noexcept {
434  return format->usage(program_name,
435  index == -1 ? "" : subcommands[index].name,
436  index == -1 ? usage : subcommands[index].usage);
437  }
438 
445  std::string format_subcommands() const noexcept {
446  return format->subcommands(subcommands);
447  }
448 
456  std::string format_options() const noexcept {
457  return format->options(*active_options);
458  }
459 
478  std::string format_help() const noexcept {
479  std::string result = format_usage();
480  result.append(1, '\n').append(format_description());
481 
482  if(active_options == &options) {
483  result.append(format_subcommands());
484 
485  if(!subcommands.empty()) {
486  result.push_back('\n');
487  }
488  }
489 
490 
491  result.append(format_options()).append(format_epilogue());
492  return result;
493  }
494 
503  void error(const std::string& message, std::ostream& err = std::cerr) {
504  err << format_usage();
505  err << format->wrap(program_name + ": error: " + message);
506  std::exit(EXIT_FAILURE);
507  }
508 };
509 } // optparse
510 } // gears
511 
512 #endif // GEARS_OPTPARSE_OPTION_PARSER_HPP