Option++  2.0
C++ library for reading command-line options
parser.hpp
Go to the documentation of this file.
1 /* Option++ -- read command-line program options
2  * Copyright (C) 2017-2020 Greg Kikola.
3  *
4  * This file is part of Option++.
5  *
6  * Option++ is free software: you can redistribute it and/or modify
7  * it under the terms of the Boost Software License version 1.0.
8  *
9  * Option++ is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * Boost Software License for more details.
13  *
14  * You should have received a copy of the Boost Software License
15  * along with Option++. If not, see
16  * <https://www.boost.org/LICENSE_1_0.txt>.
17  */
18 /* Written by Greg Kikola <[email protected]>. */
19 
25 #ifndef OPTIONPP_PARSER_HPP
26 #define OPTIONPP_PARSER_HPP
27 
28 #include <initializer_list>
29 #include <iosfwd>
30 #include <stdexcept>
31 #include <string>
32 #include <utility>
33 #include <vector>
36 #include <optionpp/utility.hpp>
37 
43 namespace optionpp {
44 
48  class parse_error : public error {
49  public:
57  parse_error(const std::string msg, const std::string fn_name,
58  const std::string option = "")
59  : error(msg, fn_name), m_option{option} {}
60 
65  const std::string& option() const noexcept { return m_option; }
66 
67  private:
68  std::string m_option; //< Option that triggered the error.
69  };
70 
87  class parser {
88  public:
89 
96  parser() noexcept {}
109  parser(const std::initializer_list<option>& il) {
110  m_groups.emplace_back("", il.begin(), il.end());
111  }
126  template <typename InputIt>
127  parser(InputIt first, InputIt last) { m_groups.emplace_back("", first, last); }
128 
137  option_group& group(const std::string& name);
138 
159  option& add_option(const option& opt = option{});
160 
172  option& add_option(const std::string& long_name,
173  char short_name = '\0',
174  const std::string& description = "",
175  const std::string& arg_name = "",
176  bool arg_required = false,
177  const std::string& group_name = "");
178 
221  template <typename InputIt>
222  parser_result parse(InputIt first, InputIt last, bool ignore_first = true) const;
223 
240  parser_result parse(int argc, char* argv[], bool ignore_first = true) const;
241 
259  parser_result parse(const std::string& cmd_line, bool ignore_first = false) const;
260 
278  void set_custom_strings(const std::string& delims,
279  const std::string& short_prefix = "",
280  const std::string& long_prefix = "",
281  const std::string& end_indicator = "",
282  const std::string& equals = "");
283 
289  void sort_groups();
290 
300  void sort_options();
301 
311  option& operator[](const std::string& long_name);
312 
322  option& operator[](char short_name);
323 
356  std::ostream& print_help(std::ostream& os,
357  int max_line_length = 78,
358  int group_indent = 0,
359  int option_indent = 2,
360  int desc_first_line_indent = 30,
361  int desc_multiline_indent = 32) const;
362 
363 
364  private:
365 
369  using group_container = std::vector<option_group>;
373  using group_iterator = group_container::iterator;
377  using group_const_iterator = group_container::const_iterator;
381  using option_iterator = option_group::iterator;
385  using option_const_iterator = option_group::const_iterator;
386 
393  group_iterator find_group(const std::string& name);
397  group_const_iterator find_group(const std::string& name) const;
398 
404  option* find_option(const std::string& long_name);
408  const option* find_option(const std::string& long_name) const;
409 
415  option* find_option(char short_name);
419  const option* find_option(char short_name) const;
420 
427  bool is_end_indicator(const std::string& argument) const noexcept {
428  return argument == m_end_of_options;
429  }
430 
437  bool is_long_option(const std::string& argument) const noexcept {
438  return argument.size() > m_long_option_prefix.size()
439  && utility::is_substr_at_pos(argument, m_long_option_prefix);
440  }
441 
448  bool is_short_option_group(const std::string& argument) const noexcept {
449  return argument.size() > m_short_option_prefix.size()
450  && utility::is_substr_at_pos(argument, m_short_option_prefix);
451  }
452 
458  bool is_non_option(const std::string& argument) const noexcept {
459  return !is_end_indicator(argument)
460  && !is_long_option(argument)
461  && !is_short_option_group(argument);
462  }
463 
474  void write_option_argument(const parsed_entry& entry) const;
475 
479  enum class cl_arg_type { non_option, //< If the argument is not an option.
480  end_indicator, //< If the argument is an end-of-options marker.
481  arg_required, //< If the argument ends with an option that needs a mandatory argument.
482  arg_optional, //< If the argument ends with an option that can take an optional argument.
483  no_arg //< If the argument ends with an option that does not take an argument (or an argument was already given).
484  };
485 
496  void parse_argument(const std::string& argument,
497  parser_result& result, cl_arg_type& type) const;
498 
512  void parse_short_option_group(const std::string& short_names,
513  const std::string& argument, bool has_arg,
514  parser_result& result, cl_arg_type& type) const;
515 
516  group_container m_groups; //< The container of option groups.
517 
518  std::string m_delims{" \t\n\r"}; //< Delimiters used to separate command-line arguments.
519  std::string m_short_option_prefix{"-"}; //< String that indicates a group of short option names.
520  std::string m_long_option_prefix{"--"}; //< String that indicates a long option name.
521  std::string m_end_of_options{"--"}; //< String that marks the end of the program options.
522  std::string m_equals{"="}; //< String used to specify an explicit argument to an option.
523  };
524 
540  std::ostream& operator<<(std::ostream& os, const parser& parser);
541 
542 } // End namespace
543 
544 /* Implementation */
545 
546 // Doxygen seems to think that the parse implementation below is a new
547 // function, so we'll ask it to skip this part of the header
548 #ifndef DOXYGEN_SHOULD_SKIP_THIS
549 
550 template <typename InputIt>
552 optionpp::parser::parse(InputIt first, InputIt last, bool ignore_first) const {
553  if (ignore_first && first != last)
554  ++first;
555 
556  InputIt it{first};
557 
558  parser_result result{};
559  cl_arg_type prev_type{cl_arg_type::non_option};
560  while (it != last) {
561  const std::string& arg{*it};
562 
563  // If we are expecting a standalone option argument...
564  if (prev_type == cl_arg_type::arg_required
565  || prev_type == cl_arg_type::arg_optional) {
566  // ...then this token should be a non-option; but if the
567  // argument is required we'll interpret it that way regardless
568  if (is_non_option(arg)
569  || prev_type == cl_arg_type::arg_required) {
570  auto& arg_info = result.back();
571  arg_info.argument = arg;
572  arg_info.original_text.push_back(' ');
573  arg_info.original_text += arg;
574  prev_type = cl_arg_type::non_option;
575  if (arg_info.opt_info)
576  write_option_argument(arg_info);
577  } else { // Found an option, reset type and continue
578  prev_type = cl_arg_type::non_option;
579  continue; // Continue without incrementing 'it' in order to reevaluate current token
580  }
581  } else if (prev_type == cl_arg_type::end_indicator) { // Ignore options
582  parsed_entry arg_info;
583  arg_info.original_text = arg;
584  arg_info.is_option = false;
585  result.push_back(std::move(arg_info));
586  } else { // Regular argument
587  parse_argument(arg, result, prev_type);
588  }
589 
590  ++it;
591  }
592 
593  // Make sure we don't still need a mandatory argument
594  if (prev_type == cl_arg_type::arg_required) {
595  const auto& opt_name = result.back().original_text;
596  throw parse_error{"option '" + opt_name + "' requires an argument",
597  "optionpp::parser::parse", opt_name};
598  }
599 
600  return result;
601 }
602 
603 #endif // DOXYGEN_SHOULD_SKIP_THIS
604 
605 #endif
optionpp::parser::set_custom_strings
void set_custom_strings(const std::string &delims, const std::string &short_prefix="", const std::string &long_prefix="", const std::string &end_indicator="", const std::string &equals="")
Change special strings used by the parser.
Definition: parser.cpp:70
optionpp::parser::parser
parser(InputIt first, InputIt last)
Construct from a sequence.
Definition: parser.hpp:127
optionpp::option_group
Holds a group of program options.
Definition: option_group.hpp:41
optionpp::parser::group
option_group & group(const std::string &name)
Returns a reference to a particular group.
Definition: parser.cpp:55
optionpp::parse_error::option
const std::string & option() const noexcept
Return option name.
Definition: parser.hpp:65
optionpp::option
Describes a valid program command-line option.
Definition: option.hpp:67
optionpp::parser::parser
parser(const std::initializer_list< option > &il)
Construct from an initializer list.
Definition: parser.hpp:109
optionpp::parser::sort_groups
void sort_groups()
Sorts the groups by name.
Definition: parser.cpp:87
optionpp::operator<<
std::ostream & operator<<(std::ostream &os, const parser &parser)
Output operator.
Definition: parser.cpp:480
optionpp::parser::print_help
std::ostream & print_help(std::ostream &os, int max_line_length=78, int group_indent=0, int option_indent=2, int desc_first_line_indent=30, int desc_multiline_indent=32) const
Print program help message.
Definition: parser.cpp:115
optionpp::parse_error
Exception class indicating an invalid option.
Definition: parser.hpp:48
optionpp::parser::parser
parser() noexcept
Default constructor.
Definition: parser.hpp:96
optionpp::option_group::iterator
container_type::iterator iterator
Plain iterator type.
Definition: option_group.hpp:59
optionpp::parser::parse
parser_result parse(InputIt first, InputIt last, bool ignore_first=true) const
Parse command-line arguments from a sequence of strings.
optionpp
Library namespace.
Definition: error.hpp:31
optionpp::parser::operator[]
option & operator[](const std::string &long_name)
Subscript operator.
Definition: parser.cpp:99
optionpp::error
Base class for library exceptions.
Definition: error.hpp:36
option_group.hpp
Header file for option_group class.
optionpp::parser::sort_options
void sort_options()
Sorts all options by name.
Definition: parser.cpp:94
optionpp::parser::add_option
option & add_option(const option &opt=option{})
Add a program option.
Definition: parser.cpp:35
optionpp::parser_result
Holds data that was parsed from the program command line.
Definition: parser_result.hpp:152
parser_result.hpp
Header file for parser_result class.
optionpp::parse_error::parse_error
parse_error(const std::string msg, const std::string fn_name, const std::string option="")
Constructor.
Definition: parser.hpp:57
optionpp::parser
Parses program options.
Definition: parser.hpp:87
utility.hpp
Header file for various utility functions.
optionpp::option_group::const_iterator
container_type::const_iterator const_iterator
Constant iterator type.
Definition: option_group.hpp:63
optionpp::utility::is_substr_at_pos
bool is_substr_at_pos(const std::string &str, const std::string &substr, std::string::size_type pos=0) noexcept
Determine if a string occurs within another string at a particular position.
Definition: utility.cpp:148