Compute Library
 22.11
CommandLineParser.h
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2017-2020, 2022 Arm Limited.
3  *
4  * SPDX-License-Identifier: MIT
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 #ifndef ARM_COMPUTE_UTILS_COMMANDLINEPARSER
25 #define ARM_COMPUTE_UTILS_COMMANDLINEPARSER
26 
27 #include "Option.h"
29 
30 #include <cstring>
31 #include <iostream>
32 #include <map>
33 #include <memory>
34 #include <memory>
35 #include <regex>
36 #include <string>
37 #include <utility>
38 #include <vector>
39 
40 namespace arm_compute
41 {
42 namespace utils
43 {
44 /** Class to parse command line arguments. */
45 class CommandLineParser final
46 {
47 public:
48  /** Default constructor. */
49  CommandLineParser() = default;
50 
51  /** Function to add a new option to the parser.
52  *
53  * @param[in] name Name of the option. Will be available under --name=VALUE.
54  * @param[in] args Option specific configuration arguments.
55  *
56  * @return Pointer to the option. The option is owned by the parser.
57  */
58  template <typename T, typename... As>
59  T *add_option(const std::string &name, As &&... args);
60 
61  /** Function to add a new positional argument to the parser.
62  *
63  * @param[in] args Option specific configuration arguments.
64  *
65  * @return Pointer to the option. The option is owned by the parser.
66  */
67  template <typename T, typename... As>
68  T *add_positional_option(As &&... args);
69 
70  /** Parses the command line arguments and updates the options accordingly.
71  *
72  * @param[in] argc Number of arguments.
73  * @param[in] argv Arguments.
74  */
75  void parse(int argc, char **argv);
76 
77  /** Validates the previously parsed command line arguments.
78  *
79  * Validation fails if not all required options are provided. Additionally
80  * warnings are generated for options that have illegal values or unknown
81  * options.
82  *
83  * @return True if all required options have been provided.
84  */
85  bool validate() const;
86 
87  /** Prints a help message for all configured options.
88  *
89  * @param[in] program_name Name of the program to be used in the help message.
90  */
91  void print_help(const std::string &program_name) const;
92 
93 private:
94  using OptionsMap = std::map<std::string, std::unique_ptr<Option>>;
95  using PositionalOptionsVector = std::vector<std::unique_ptr<Option>>;
96 
97  OptionsMap _options{};
98  PositionalOptionsVector _positional_options{};
99  std::vector<std::string> _unknown_options{};
100  std::vector<std::string> _invalid_options{};
101 };
102 
103 template <typename T, typename... As>
104 inline T *CommandLineParser::add_option(const std::string &name, As &&... args)
105 {
106  auto result = _options.emplace(name, std::make_unique<T>(name, std::forward<As>(args)...));
107  return static_cast<T *>(result.first->second.get());
108 }
109 
110 template <typename T, typename... As>
112 {
113  _positional_options.emplace_back(std::make_unique<T>(std::forward<As>(args)...));
114  return static_cast<T *>(_positional_options.back().get());
115 }
116 
117 inline void CommandLineParser::parse(int argc, char **argv)
118 {
119  const std::regex option_regex{ "--((?:no-)?)([^=]+)(?:=(.*))?" };
120 
121  const auto set_option = [&](const std::string & option, const std::string & name, const std::string & value)
122  {
123  if(_options.find(name) == _options.end())
124  {
125  _unknown_options.push_back(option);
126  return;
127  }
128 
129  const bool success = _options[name]->parse(value);
130 
131  if(!success)
132  {
133  _invalid_options.push_back(option);
134  }
135  };
136 
137  unsigned int positional_index = 0;
138 
139  for(int i = 1; i < argc; ++i)
140  {
141  std::string mixed_case_opt{ argv[i] };
142  int equal_sign = mixed_case_opt.find('=');
143  int pos = (equal_sign == -1) ? strlen(argv[i]) : equal_sign;
144 
145  const std::string option = arm_compute::utility::tolower(mixed_case_opt.substr(0, pos)) + mixed_case_opt.substr(pos);
146  std::smatch option_matches;
147 
148  if(std::regex_match(option, option_matches, option_regex))
149  {
150  // Boolean option
151  if(option_matches.str(3).empty())
152  {
153  set_option(option, option_matches.str(2), option_matches.str(1).empty() ? "true" : "false");
154  }
155  else
156  {
157  // Can't have "no-" and a value
158  if(!option_matches.str(1).empty())
159  {
160  _invalid_options.emplace_back(option);
161  }
162  else
163  {
164  set_option(option, option_matches.str(2), option_matches.str(3));
165  }
166  }
167  }
168  else
169  {
170  if(positional_index >= _positional_options.size())
171  {
172  _invalid_options.push_back(mixed_case_opt);
173  }
174  else
175  {
176  _positional_options[positional_index]->parse(mixed_case_opt);
177  ++positional_index;
178  }
179  }
180  }
181 }
182 
183 inline bool CommandLineParser::validate() const
184 {
185  bool is_valid = true;
186 
187  for(const auto &option : _options)
188  {
189  if(option.second->is_required() && !option.second->is_set())
190  {
191  is_valid = false;
192  std::cerr << "ERROR: Option '" << option.second->name() << "' is required but not given!\n";
193  }
194  }
195 
196  for(const auto &option : _positional_options)
197  {
198  if(option->is_required() && !option->is_set())
199  {
200  is_valid = false;
201  std::cerr << "ERROR: Option '" << option->name() << "' is required but not given!\n";
202  }
203  }
204 
205  for(const auto &option : _unknown_options)
206  {
207  std::cerr << "WARNING: Skipping unknown option '" << option << "'!\n";
208  }
209 
210  for(const auto &option : _invalid_options)
211  {
212  std::cerr << "WARNING: Skipping invalid option '" << option << "'!\n";
213  }
214 
215  return is_valid;
216 }
217 
218 inline void CommandLineParser::print_help(const std::string &program_name) const
219 {
220  std::cout << "usage: " << program_name << " \n";
221 
222  for(const auto &option : _options)
223  {
224  std::cout << option.second->help() << "\n";
225  }
226 
227  for(const auto &option : _positional_options)
228  {
229  std::string help_to_print;
230 
231  // Extract help sub-string
232  const std::string help_str = option->help();
233  const size_t help_pos = help_str.find(" - ");
234  if(help_pos != std::string::npos)
235  {
236  help_to_print = help_str.substr(help_pos);
237  }
238 
239  std::cout << option->name() << help_to_print << "\n";
240  }
241 }
242 } // namespace utils
243 } // namespace arm_compute
244 #endif /* ARM_COMPUTE_UTILS_COMMANDLINEPARSER */
bool validate() const
Validates the previously parsed command line arguments.
Class to parse command line arguments.
Copyright (c) 2017-2022 Arm Limited.
T * add_option(const std::string &name, As &&... args)
Function to add a new option to the parser.
std::string tolower(std::string string)
Convert string to lower case.
Definition: Utility.h:205
void parse(int argc, char **argv)
Parses the command line arguments and updates the options accordingly.
const char * name
T * add_positional_option(As &&... args)
Function to add a new positional argument to the parser.
void print_help(const std::string &program_name) const
Prints a help message for all configured options.
CommandLineParser()=default
Default constructor.