Ookii.CommandLine for C++  1.0.0
command_line_core.h
Go to the documentation of this file.
1 #ifndef OOKII_COMMAND_LINE_CORE_H_
7 #define OOKII_COMMAND_LINE_CORE_H_
8 
9 #pragma once
10 
11 #include <iterator>
12 #include <map>
13 #include "command_line_argument.h"
14 #include "parse_result.h"
15 #include "owned_or_borrowed_ptr.h"
16 
22 namespace ookii
23 {
24  namespace details
25  {
26 #ifdef _UNICODE
27  using default_char_type = wchar_t;
28 #else
29  using default_char_type = char;
30 #endif
31 
32  template<typename CharType, typename Traits, typename Alloc>
33  struct parser_storage
34  {
35  using string_type = std::basic_string<CharType, Traits, Alloc>;
36 
37  parser_storage(string_type command_name)
38  : command_name(command_name)
39  {
40  }
41 
42  string_type command_name;
43  string_type description;
44  std::vector<string_type> prefixes;
45  std::locale locale;
46  CharType argument_value_separator{':'};
47  bool allow_white_space_separator{true};
48  bool allow_duplicate_arguments{false};
49  };
50  }
51 
54  enum class on_parsed_action
55  {
57  none,
63  };
64 
90  template<typename CharType = details::default_char_type, typename Traits = std::char_traits<CharType>, typename Alloc = std::allocator<CharType>>
92  {
93  public:
99  using string_view_type = std::basic_string_view<CharType, Traits>;
105  using storage_type = details::parser_storage<CharType, Traits, Alloc>;
108 
112  template<typename T>
114 
118  template<typename T>
120 
126  static constexpr std::vector<string_type> get_default_prefixes()
127  {
128  std::vector<string_type> result;
129 
130 #ifdef _WIN32
131  result.reserve(2);
132 #else
133  result.reserve(1);
134 #endif
135 
136  constexpr auto prefix1 = literal_cast<CharType>("-");
137  result.push_back(prefix1.data());
138 
139 #ifdef _WIN32
140  constexpr auto prefix2 = literal_cast<CharType>("/");
141  result.push_back(prefix2.data());
142 #endif
143 
144  return result;
145  }
146 
157  template<typename Range>
158  basic_command_line_parser(const Range &arguments, storage_type &&storage, bool case_sensitive)
159  : _storage{std::move(storage)},
160  _arguments{string_less{case_sensitive, storage.locale}}
161  {
162  for (const auto &argument : arguments)
163  {
164  auto actual_arg = argument->to_argument();
165  auto name = actual_arg->name();
166  const auto [it, success] = _arguments.insert(std::pair{name, std::move(actual_arg)});
167  if (!success)
168  throw std::logic_error("Duplicate argument name.");
169 
170  if (it->second->position())
171  {
172  // Usually, positional arguments should be in order in the builder, but it's
173  // technically possible for the caller to have stored references to the argument
174  // builders and called positional() on them in a different order than they were
175  // created, so make sure the resulting list is ordered correctly regardless.
176  const auto pos = std::lower_bound(_positional_arguments.begin(),
177  _positional_arguments.end(),
178  it->second,
179  [](const auto &left, const auto &right)
180  {
181  return *left->position() < *right->position();
182  });
183 
184  _positional_arguments.insert(pos, it->second.get());
185  }
186 
187  for (const auto &alias : it->second->aliases())
188  {
189  // Add the aliases to the arguments list using borrowed pointers.
190  const auto [alias_it, alias_success] = _arguments.insert(std::pair{alias, it->second.as_borrowed()});
191  if (!alias_success)
192  throw std::logic_error("Duplicate argument name.");
193  }
194  }
195  }
196 
200  const string_type &command_name() const noexcept
201  {
202  return _storage.command_name;
203  }
204 
208  const string_type &description() const noexcept
209  {
210  return _storage.command_name;
211  }
212 
216  bool allow_white_space_separator() const noexcept
217  {
218  return _storage.allow_white_space_separator;
219  }
220 
224  bool allow_duplicate_arguments() const noexcept
225  {
226  return _storage.allow_duplicate_arguments;
227  }
228 
232  CharType argument_value_separator() const noexcept
233  {
234  return _storage.argument_value_separator;
235  }
236 
242  const std::vector<string_type> &prefixes() const noexcept
243  {
244  return _storage.prefixes;
245  }
246 
250  const std::locale &locale() const noexcept
251  {
252  return _storage.locale;
253  }
254 
260  auto arguments() const
261  {
262  return _arguments | std::views::filter([](const auto &a)
263  {
264  // Borrowed pointers are aliases and shouldn't be returned here.
265  return a.second.is_owned();
266  })
267  | std::views::transform([](const auto &a) -> auto&
268  {
269  return *a.second;
270  });
271  }
272 
277  {
278  return _positional_arguments.size();
279  }
280 
290  const argument_base_type &get_argument(size_t pos) const
291  {
292  return *_positional_arguments.at(pos);
293  }
294 
302  const argument_base_type &get_argument(const string_type &name) const
303  {
304  return *_arguments.at(name);
305  }
306 
317  template<typename Iterator>
318  [[nodiscard]] result_type parse(Iterator begin, Iterator end)
319  {
320  for (auto &arg : _arguments)
321  arg.second->reset();
322 
323  size_t position = 0;
324  for (auto current = begin; current != end; ++current)
325  {
326  auto arg = *current;
327  auto prefix_length = check_prefix(arg);
328  if (prefix_length > 0)
329  {
330  // Current is updated if parsing used the next argument for a value.
331  auto result = parse_named_argument(arg, current, end, prefix_length);
332  if (!result)
333  return result;
334  }
335  else
336  {
337  // If this is a multi-value argument then we're on the last argument, otherwise search for the next argument without a value
338  // Skip positional arguments that have already been specified by name
339  while (position < _positional_arguments.size() &&
340  !_positional_arguments[position]->is_multi_value() &&
341  _positional_arguments[position]->has_value())
342  {
343  ++position;
344  }
345 
346  if (position >= _positional_arguments.size())
348 
349  auto result = set_argument_value(*_positional_arguments[position], arg);
350  if (!result)
351  return result;
352  }
353  }
354 
355  result_type result;
356  for_each_argument_in_usage_order([&result](auto &arg)
357  {
358  if (arg.is_required())
359  {
360  if (!arg.has_value())
361  {
362  result = {parse_error::missing_required_argument, arg.name()};
363  return false;
364  }
365  }
366  else
367  {
368  arg.apply_default_value();
369  }
370 
371  return true;
372  });
373 
374  return result;
375  }
376 
390  template<typename Iterator>
391  result_type parse(Iterator begin, Iterator end, const usage_options_type &options)
392  {
393  auto result = parse(begin, end);
394  handle_error(result, options);
395  return result;
396  }
397 
407  template<typename Range>
408  result_type parse(const Range &range)
409  {
410  // Using allows ADL.
411  using std::begin;
412  using std::end;
413  return parse(begin(range), end(range));
414  }
415 
428  template<typename Range>
429  result_type parse(Range range, const usage_options_type &options)
430  {
431  auto result = parse(range);
432  handle_error(result, options);
433  return result;
434  }
435 
445  template<typename T>
446  result_type parse(std::initializer_list<T> args)
447  {
448  return parse(args.begin(), args.end());
449  }
450 
463  template<typename T>
464  result_type parse(std::initializer_list<T> args, const usage_options_type &options)
465  {
466  auto result = parse(args);
467  handle_error(result, options);
468  return result;
469  }
470 
480  result_type parse(int argc, const CharType *const argv[])
481  {
482  if (argc < 1)
483  return parse<const CharType *>({});
484  else
485  return parse(argv + 1, argv + argc);
486  }
487 
500  result_type parse(int argc, const CharType *const argv[], const usage_options_type &options)
501  {
502  auto result = parse(argc, argv);
503  handle_error(result, options);
504  return result;
505  }
506 
513  void write_usage(const usage_options_type &options = {})
514  {
515  if (!_storage.description.empty())
516  {
517  options.output << _storage.description << std::endl << std::endl;
518  }
519 
520  write_usage_syntax(options);
521  write_usage_descriptions(options);
522  }
523 
535  template<typename Func>
537  {
538  // First the positional arguments
539  for (auto &arg : _positional_arguments)
540  {
541  auto result = f(*arg);
542  if (!result)
543  return result;
544  }
545 
546  // Then required non-positional arguments
547  for (auto &arg : _arguments)
548  {
549  if (arg.second.is_owned() && !arg.second->position() && arg.second->is_required())
550  {
551  auto result = f(*arg.second);
552  if (!result)
553  return result;
554  }
555  }
556 
557  // Then the optional non-positional arguments
558  for (auto &arg : _arguments)
559  {
560  if (arg.second.is_owned() && !arg.second->position() && !arg.second->is_required())
561  {
562  auto result = f(*arg.second);
563  if (!result)
564  return result;
565  }
566  }
567 
568  return true;
569  }
570 
586  {
587  _on_parsed_callback = callback;
588  }
589 
590  private:
591  void handle_error(const result_type &result, const usage_options_type &options)
592  {
593  if (!result)
594  {
595  // If parsing is cancelled that is not really an error, so we just show usage in
596  // that case.
597  if (result.error != parse_error::parsing_cancelled)
598  {
599  options.error << result.get_error_message(options.error.rdbuf()->getloc()) << std::endl << std::endl;
600  }
601 
602  write_usage(options);
603  }
604  }
605 
606  size_t check_prefix(string_view_type argument)
607  {
608  // Even if the named argument switch is '-', we treat a '-' followed by a digit as a value, because it could denote a negative number.
609  if (argument.length() >= 2 && argument[0] == '-' && std::isdigit(argument[1], _storage.locale))
610  return 0;
611 
612  for (auto &prefix : _storage.prefixes)
613  {
614  if (argument.starts_with(prefix))
615  return prefix.length();
616  }
617 
618  return 0;
619  }
620 
621  template<typename Iterator>
622  result_type parse_named_argument(string_view_type arg, Iterator &current, Iterator end, size_t prefix_length)
623  {
624  string_view_type name;
625  string_view_type value;
626  bool has_value = false;
627 
628  auto separator_index = arg.find(_storage.argument_value_separator);
629  if (separator_index == string_view_type::npos)
630  {
631  name = arg.substr(prefix_length);
632  }
633  else
634  {
635  name = arg.substr(prefix_length, separator_index - prefix_length);
636  value = arg.substr(separator_index + 1);
637  has_value = true;
638  }
639 
640  auto it = this->_arguments.find(name);
641  if (it == this->_arguments.end())
642  return {parse_error::unknown_argument, string_type{name}};
643 
644  if (!has_value)
645  {
646  if (it->second->set_switch_value())
647  {
648  return post_process_argument(*it->second, {});
649  }
650 
651  auto value_it = current;
652  if (_storage.allow_white_space_separator && ++value_it != end)
653  {
654  current = value_it;
655  value = *current;
656  // If the next argument looks like an argument name, don't use it as value.
657  has_value = check_prefix(value) == 0;
658  }
659  }
660 
661  if (has_value)
662  {
663  if (!_storage.allow_duplicate_arguments && !it->second->is_multi_value() && it->second->has_value())
664  return {parse_error::duplicate_argument, it->second->name()};
665 
666  return set_argument_value(*it->second, value);
667  }
668  else
669  {
670  return {parse_error::missing_value, it->second->name()};
671  }
672  }
673 
674  result_type set_argument_value(argument_base_type &arg, string_view_type value)
675  {
676  if (!arg.set_value(value, _storage.locale))
677  return {parse_error::invalid_value, arg.name()};
678 
679  return post_process_argument(arg, value);
680  }
681 
682  result_type post_process_argument(argument_base_type &arg, string_view_type value)
683  {
684  auto action = on_parsed_action::none;
685  if (_on_parsed_callback)
686  action = _on_parsed_callback(arg, value);
687 
688  if (action == on_parsed_action::cancel_parsing ||
689  (arg.cancel_parsing() && action != on_parsed_action::always_continue))
690  {
691  return {parse_error::parsing_cancelled, arg.name()};
692  }
693 
694  return {parse_error::none};
695  }
696 
697  void write_usage_syntax(const usage_options_type &options = {})
698  {
699  auto &stream = options.output;
700  stream << reset_indent << set_indent(options.syntax_indent);
701  stream << format::ncformat(_storage.locale, options.usage_prefix_format, _storage.command_name);
702  for_each_argument_in_usage_order([this, &options, &stream](const auto &arg)
703  {
704  auto syntax = _storage.prefixes[0] + arg.name();
705  if (arg.position())
706  syntax = format::ncformat(_storage.locale, options.optional_argument_format, syntax);
707 
708  if (!arg.is_switch())
709  {
710  CharType separator = (_storage.allow_white_space_separator && options.use_white_space_value_separator) ? ' ' : _storage.argument_value_separator;
711  auto value = format::ncformat(_storage.locale, options.value_description_format, arg.value_description());
712  syntax += separator + value;
713  }
714 
715  if (arg.is_multi_value())
716  syntax += options.multi_value_suffix;
717 
718  if (!arg.is_required())
719  syntax = format::ncformat(_storage.locale, options.optional_argument_format, syntax);
720 
721  stream << ' ' << syntax;
722  return true;
723  });
724 
725  stream << std::endl << std::endl;
726  }
727 
728  void write_usage_descriptions(const usage_options_type &options = {})
729  {
730  auto &stream = options.output;
731  stream << reset_indent << set_indent(options.argument_description_indent);
732  for_each_argument_in_usage_order([this, &options, &stream](const auto &arg)
733  {
734  if (arg.description().empty())
735  return true;
736 
737  auto value_description = format::ncformat(_storage.locale, options.value_description_format, arg.value_description());
738  if (arg.is_switch())
739  value_description = format::ncformat(_storage.locale, options.optional_argument_format, value_description);
740 
741  auto default_value = arg.format_default_value(options, _storage.locale);
742 
743  string_type aliases;
744  if (options.include_aliases_in_description && !arg.aliases().empty())
745  {
746  bool first = true;
747  for (const auto &alias : arg.aliases())
748  {
749  if (first)
750  first = false;
751  else
752  aliases += options.alias_separator;
753 
754  aliases += _storage.prefixes[0] + alias;
755  }
756 
757  aliases = format::ncformat(_storage.locale, options.alias_format, aliases);
758  }
759 
760  stream << reset_indent << format::ncformat(_storage.locale, options.argument_description_format,
761  _storage.prefixes[0], arg.name(), value_description, aliases, arg.description(), default_value)
762  << std::endl;
763 
764  return true;
765  });
766  }
767 
768  storage_type _storage;
769 
770  // _positional_arguments contains pointers to items owned by _arguments. Since the two
771  // have the same lifetime, this is okay.
772  std::map<string_type, owned_or_borrowed_ptr<argument_base_type>, string_less> _arguments;
773  std::vector<argument_base_type *> _positional_arguments;
774  on_parsed_callback _on_parsed_callback;
775  };
776 
781 
782 }
783 
784 #endif
Parses command line arguments into strongly-typed values.
Definition: command_line_core.h:92
result_type parse(int argc, const CharType *const argv[])
Parses the provided arguments.
Definition: command_line_core.h:480
std::function< on_parsed_action(argument_base_type &, string_view_type value)> on_parsed_callback
The callback function type for on_parsed().
Definition: command_line_core.h:107
CharType argument_value_separator() const noexcept
Definition: command_line_core.h:232
result_type parse(std::initializer_list< T > args)
Parses the arguments in the specified initializer list.
Definition: command_line_core.h:446
void write_usage(const usage_options_type &options={})
Writes usage help for this parser's arguments.
Definition: command_line_core.h:513
const argument_base_type & get_argument(size_t pos) const
Gets an argument by position.
Definition: command_line_core.h:290
void on_parsed(on_parsed_callback callback)
Sets a callback that will be invoked every time an argument is parsed.
Definition: command_line_core.h:585
typename argument_type< T >::typed_storage_type::converter_type converter_type
The specialized type of the custom command line converter function used.
Definition: command_line_core.h:119
result_type parse(std::initializer_list< T > args, const usage_options_type &options)
Parses the arguments in the specified initializer list, and writes error and usage information to the...
Definition: command_line_core.h:464
const string_type & command_name() const noexcept
Returns the command name used when generating usage help.
Definition: command_line_core.h:200
basic_command_line_parser(const Range &arguments, storage_type &&storage, bool case_sensitive)
Creates a new instance of the basic_command_line_parser class.
Definition: command_line_core.h:158
static constexpr std::vector< string_type > get_default_prefixes()
Gets the default prefixes accepted by the parser.
Definition: command_line_core.h:126
const string_type & description() const noexcept
Returns the description used when generating usage help.
Definition: command_line_core.h:208
size_t positional_argument_count() const
Gets the number of positional arguments.
Definition: command_line_core.h:276
const std::vector< string_type > & prefixes() const noexcept
Gets a list of all the argument name prefixes accepted by the parser.
Definition: command_line_core.h:242
const std::locale & locale() const noexcept
Gets the locale used to parse argument values and to format strings.
Definition: command_line_core.h:250
bool allow_duplicate_arguments() const noexcept
Indicates whether duplicate arguments are allowed.
Definition: command_line_core.h:224
result_type parse(Iterator begin, Iterator end, const usage_options_type &options)
Parses the arguments in the range specified by the iterators, and writes error and usage information ...
Definition: command_line_core.h:391
details::parser_storage< CharType, Traits, Alloc > storage_type
The specialized type of parser parameter storage used. For internal use.
Definition: command_line_core.h:105
result_type parse(const Range &range)
Parses the arguments in the specified range.
Definition: command_line_core.h:408
result_type parse(int argc, const CharType *const argv[], const usage_options_type &options)
Parses the provided arguments, and writes error and usage information to the console if a parsing err...
Definition: command_line_core.h:500
bool allow_white_space_separator() const noexcept
Indicates whether argument names and values can be separated by white space.
Definition: command_line_core.h:216
std::basic_string_view< CharType, Traits > string_view_type
The specialized type of std::basic_string_view used.
Definition: command_line_core.h:99
bool for_each_argument_in_usage_order(Func f) const
Invokes the specified function on each argument in the order they are shown in in the usage help.
Definition: command_line_core.h:536
auto arguments() const
Gets a view of all the arguments defined by the parser.
Definition: command_line_core.h:260
const argument_base_type & get_argument(const string_type &name) const
Gets an argument by name.
Definition: command_line_core.h:302
result_type parse(Range range, const usage_options_type &options)
Parses the arguments in the specified range, and writes error and usage information to the console if...
Definition: command_line_core.h:429
typename argument_base_type::string_type string_type
The specialized type of std::basic_string used.
Definition: command_line_core.h:97
result_type parse(Iterator begin, Iterator end)
Parses the arguments in the range specified by the iterators.
Definition: command_line_core.h:318
Provides options for how to format usage help.
Definition: usage_options.h:29
Abstract base class for regular and multi-value arguments.
Definition: command_line_argument.h:75
typename storage_type::string_type string_type
The concrete type of std::basic_string used.
Definition: command_line_argument.h:80
Class that provides information about arguments that are not multi-value arguments.
Definition: command_line_argument.h:313
Provides the ookii::command_line_argument class.
std::string ncformat(const std::locale &loc, std::string_view format, Args &&... args)
Helper to format using a non-const format string without needing to explicitly construct a format_arg...
Definition: format_helper.h:55
Namespace containing the core Ookii.CommandLine.Cpp types.
Definition: command_line_argument.h:16
std::basic_ostream< CharType, Traits > & reset_indent(std::basic_ostream< CharType, Traits > &stream)
IO manipulator that lets the next line start at the beginning of the line, without indenting it.
Definition: line_wrapping_stream.h:515
details::set_indent_helper set_indent(size_t indent)
IO manipulator that changes the number of spaces that each line is indented with for a line wrapping ...
Definition: line_wrapping_stream.h:493
on_parsed_action
Value to be returned from the callback passed to the basic_command_line_parser::on_parsed() method.
Definition: command_line_core.h:55
@ always_continue
Continue parsing even if command_line_argument::cancel_parsing() returns true.
@ none
Don't take any special action.
@ cancel_parsing
Cancel parsing immediately, disregarding the rest of the command line. Parsing will return with parse...
@ invalid_value
A supplied value could not be converted to the argument's type.
@ parsing_cancelled
Parsing was cancelled by an argument using basic_parser_builder::argument_builder::cancel_parsing(),...
@ none
No error occurred.
@ unknown_argument
An argument name was supplied that doesn't exist.
@ duplicate_argument
An argument, other than a multi-value argument, was supplied more than once, and basic_parser_builder...
@ missing_value
A named argument, other than a switch argument, was supplied without a value.
@ too_many_arguments
More positional arguments were supplied than were defined.
Provides a smart pointer that can optionally own the contained pointer.
Provides error handling for the ookii::basic_command_line_parser class.
Provides the result, success or error, of a command line argument parsing operation.
Definition: parse_result.h:74
A version of the std::less predicate for strings that supports case insensitive comparison.
Definition: string_helper.h:23