HAL
program_options.cpp
Go to the documentation of this file.
2 
5 
6 #include <algorithm>
7 #include <unistd.h>
8 
9 #if __linux__ || __APPLE__
10 #include <sys/ioctl.h>
11 #endif
12 
13 namespace hal
14 {
15  const std::string ProgramOptions::A_REQUIRED_PARAMETER = "__A_REQUIRED_PARAMETER__";
16 
18  {
19  m_name = name;
20  }
21 
22  std::vector<std::string> ProgramOptions::get_unknown_arguments()
23  {
24  return m_unknown_options;
25  }
26 
27  ProgramArguments ProgramOptions::parse(int argc, const char* argv[])
28  {
29  const Option* current_option = nullptr;
30 
31  m_unknown_options.clear();
32 
33  auto all_options = get_all_options();
34 
35  std::string current_flag;
36  std::vector<std::string> current_parameters;
37  size_t param_pos = 0;
38 
39  ProgramArguments args(argc, argv);
40 
41  // i=1: ignore first argument as it is the program itself
42  for (int i = 1; i < argc; ++i)
43  {
44  std::string arg(argv[i]);
45 
46  // search through all options for a recognized flag
47  bool opt_found = false;
48  for (auto opt : all_options)
49  {
50  if (std::find(opt->flags.begin(), opt->flags.end(), arg) != opt->flags.end())
51  {
52  // flag found, set current option
53 
54  if (current_option != nullptr)
55  {
56  if (current_option->parameters[param_pos] == A_REQUIRED_PARAMETER)
57  {
58  die("core",
59  "the option with flags {}is missing a required parameter!",
60  utils::join(" ", std::vector<std::string>(current_option->flags.begin(), current_option->flags.end())));
61  }
62  else
63  {
64  args.set_option(current_flag, current_option->flags, current_parameters);
65  }
66  }
67 
68  opt_found = true;
69  current_option = opt;
70  current_parameters = opt->parameters;
71  current_flag = arg;
72  param_pos = 0;
73  break;
74  }
75  }
76 
77  // if the current string is not a flag, process it otherwise
78  if (!opt_found)
79  {
80  // if the current option expects parameters, add the current string
81  if (current_option != nullptr)
82  {
83  current_parameters[param_pos++] = arg;
84  }
85  else
86  {
87  m_unknown_options.push_back(arg);
88  }
89  }
90 
91  // if the current option got all expected parameters, it is finished
92  if (current_option != nullptr && param_pos >= current_option->parameters.size())
93  {
94  args.set_option(current_flag, current_option->flags, current_parameters);
95  current_option = nullptr;
96  current_parameters.clear();
97  current_flag = "";
98  param_pos = 0;
99  }
100  }
101 
102  if (current_option != nullptr)
103  {
104  if (param_pos < current_option->parameters.size() && current_option->parameters[param_pos] == A_REQUIRED_PARAMETER)
105  {
106  die("core",
107  "the option with flags {}is missing at least one required parameter!",
108  utils::join(" ", std::vector<std::string>(current_option->flags.begin(), current_option->flags.end())));
109  }
110 
111  args.set_option(current_flag, current_option->flags, current_parameters);
112  }
113 
114  return args;
115  }
116 
117  bool ProgramOptions::is_registered(const std::string& flag) const
118  {
119  for (auto opt : get_all_options())
120  {
121  if (std::find(opt->flags.begin(), opt->flags.end(), flag) != opt->flags.end())
122  {
123  return true;
124  }
125  }
126  return false;
127  }
128 
129  bool ProgramOptions::add(const std::string& flag, const std::string& description, const std::initializer_list<std::string>& parameters)
130  {
131  return add({flag}, description, parameters);
132  }
133 
134  bool ProgramOptions::add(const std::initializer_list<std::string>& flags, const std::string& description, const std::initializer_list<std::string>& parameters)
135  {
136  if (flags.size() == 0)
137  {
138  log_error("core", "can't add option with empty flags (description: '{}')!", utils::trim(description));
139  return false;
140  }
141 
142  if (description.empty())
143  {
144  std::string flags_string = "";
145  for (const auto& flag : flags)
146  {
147  flags_string += flag + " ";
148  }
149 
150  log_error("core", "can't add option with flags ({}). The description must not be empty!", utils::trim(flags_string));
151  return false;
152  }
153 
154  // look for already registered option, abort if found
155  for (auto opt : get_all_options())
156  {
157  if (opt->description == description)
158  {
159  std::string flags_string = "";
160  for (const auto& flag : flags)
161  {
162  flags_string += flag + " ";
163  }
164 
165  log_error("core", "can't add option with flags ({}). An option with description '{}' is already registered.", utils::trim(flags_string), description);
166  return false;
167  }
168  }
169 
170  for (const auto& flag : flags)
171  {
172  if (is_registered(flag))
173  {
174  log_error("core", "the flag '{}' is already registered.", flag);
175  return false;
176  }
177  }
178 
179  // only allow REQUIRED, REQUIRED, REQUIRED, [...], NON_REQUIRED, NON_REQUIRED, [...]
180  bool found_not_required = false;
181  for (const auto& param : parameters)
182  {
183  if (param == A_REQUIRED_PARAMETER)
184  {
185  if (found_not_required)
186  {
187  log_error("core", "a required parameter can't follow a non-required parameter.");
188  return false;
189  }
190  }
191  else if (param != A_REQUIRED_PARAMETER)
192  {
193  found_not_required = true;
194  }
195  }
196 
197  // create new option
198  Option opt;
199  opt.description = description;
200  opt.parameters = parameters;
201  opt.flags = flags;
202 
203  m_options.push_back(opt);
204 
205  return true;
206  }
207 
208  bool ProgramOptions::add(const ProgramOptions& other_options, const std::string& category)
209  {
210  // look for conflicts
211  for (auto other_opt : other_options.get_all_options())
212  {
213  for (auto opt : get_all_options())
214  {
215  if (opt->description == other_opt->description)
216  {
217  log_error("core", "an option with description '{}' is already registered.", opt->description);
218  return false;
219  }
220 
221  for (const auto& flag : other_opt->flags)
222  {
223  if (std::find(opt->flags.begin(), opt->flags.end(), flag) != opt->flags.end())
224  {
225  log_error("core", "the flag '{}' is already registered.", flag);
226  return false;
227  }
228  }
229  }
230  }
231 
232  // no conflict -> add
233  m_suboptions[category].push_back(other_options);
234  return true;
235  }
236 
237  bool ProgramOptions::remove(const std::string& flag)
238  {
239  for (auto it = m_options.begin(); it != m_options.end(); ++it)
240  {
241  if (it->flags.find(flag) != it->flags.end())
242  {
243  it->flags.erase(flag);
244  if (it->flags.empty())
245  {
246  m_options.erase(it);
247  }
248  return true;
249  }
250  }
251  for (auto& sub : m_suboptions)
252  {
253  for (auto& opt : sub.second)
254  {
255  auto success = opt.remove(flag);
256  if (success)
257  {
258  return true;
259  }
260  }
261  }
262  return false;
263  }
264 
266  {
267  size_t line_width = 80; // default magic number
268 
269 #if __linux__ || __APPLE__
270  struct winsize w;
271  ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
272  if (w.ws_col < 30)
273  {
274  line_width = 1000;
275  }
276  else
277  {
278  line_width = w.ws_col;
279  }
280 #endif
281 
282  return get_options_string_internal(get_flag_length() + 10, line_width);
283  }
284 
285  std::vector<std::tuple<std::set<std::string>, std::string>> ProgramOptions::get_options() const
286  {
287  std::vector<std::tuple<std::set<std::string>, std::string>> options;
288  for (auto opt : get_all_options())
289  {
290  options.push_back(std::make_tuple(opt->flags, opt->description));
291  }
292  return options;
293  }
294 
295  std::vector<const ProgramOptions::Option*> ProgramOptions::get_all_options() const
296  {
297  std::vector<const ProgramOptions::Option*> options;
298 
299  std::transform(m_options.begin(), m_options.end(), std::back_inserter(options), [](auto& opt) { return &opt; });
300 
301  for (const auto& it : m_suboptions)
302  {
303  for (const auto& opt : it.second)
304  {
305  auto insertion = opt.get_all_options();
306  options.insert(options.end(), insertion.begin(), insertion.end());
307  }
308  }
309  return options;
310  }
311 
312  size_t ProgramOptions::get_flag_length() const
313  {
314  size_t max_len = 0;
315  for (const auto& opt : m_options)
316  {
317  size_t opt_len = 0;
318  for (const auto& f : opt.flags)
319  {
320  opt_len += f.size() + 2;
321  }
322  for (size_t j = 0; j < opt.parameters.size(); ++j)
323  {
324  opt_len += 4; //" arg"
325  }
326  opt_len -= 2; // last option has no ", "
327 
328  if (opt_len > max_len)
329  {
330  max_len = opt_len;
331  }
332  }
333 
334  for (const auto& it : m_suboptions)
335  {
336  for (const auto& opt : it.second)
337  {
338  size_t subopt_len = opt.get_flag_length();
339  if (subopt_len > max_len)
340  {
341  max_len = subopt_len;
342  }
343  }
344  }
345 
346  return max_len;
347  }
348 
349  std::string ProgramOptions::get_options_string_internal(size_t fill_length, size_t max_line_width) const
350  {
351  std::string s = "";
352 
353  if (!m_name.empty())
354  {
355  s += m_name + "\n";
356  }
357 
358  for (size_t i = 0; i < m_options.size(); ++i)
359  {
360  auto& opt = m_options[i];
361 
362  std::string flags = " " + utils::join(", ", std::vector<std::string>(opt.flags.begin(), opt.flags.end()));
363 
364  for (const auto& flag : opt.parameters)
365  {
366  if (flag == A_REQUIRED_PARAMETER)
367  {
368  flags += " ARG";
369  }
370  else
371  {
372  flags += " arg";
373  }
374  }
375 
376  s += flags;
377 
378  for (size_t j = 0; j < fill_length - flags.size(); ++j)
379  {
380  s += " ";
381  }
382 
383  std::string description = opt.description;
384  bool first_description_line = true;
385 
386  while (!description.empty())
387  {
388  if (!first_description_line)
389  {
390  for (size_t j = 0; j < fill_length; ++j)
391  {
392  s += " ";
393  }
394  }
395  if (description.size() > max_line_width - fill_length)
396  {
397  auto index = description.substr(0, max_line_width - fill_length).rfind(' ');
398  if (index == std::string::npos)
399  {
400  index = max_line_width - fill_length;
401  }
402  s += description.substr(0, index) + "\n";
403  description = description.substr(index);
404  }
405  else
406  {
407  s += description;
408  description = "";
409  }
410  first_description_line = false;
411  }
412 
413  if (i != m_options.size() - 1)
414  {
415  s += "\n";
416  }
417  }
418 
419  if (!s.empty())
420  {
421  s += "\n";
422  }
423 
424  for (const auto& it : m_suboptions)
425  {
426  if (!it.first.empty())
427  {
428  s += it.first + "\n";
429  }
430  for (const auto& opt : it.second)
431  {
432  s += opt.get_options_string_internal(fill_length, max_line_width);
433  }
434  }
435 
436  if (s.length() > 1)
437  {
438  while (s.substr(s.length() - 2) != "\n\n")
439  {
440  s += "\n";
441  }
442  }
443  return s;
444  }
445 } // namespace hal
std::string get_options_string() const
bool is_registered(const std::string &flag) const
bool remove(const std::string &flag)
std::vector< std::tuple< std::set< std::string >, std::string > > get_options() const
ProgramArguments parse(int argc, const char *argv[])
bool add(const std::string &flag, const std::string &description, const std::initializer_list< std::string > &parameters={})
std::vector< std::string > get_unknown_arguments()
ProgramOptions(const std::string &name="")
static const std::string A_REQUIRED_PARAMETER
constant to specify that a parameter is required and does not have a default value.
#define die(channel,...)
Definition: log.h:94
#define log_error(channel,...)
Definition: log.h:78
CORE_API T trim(const T &s, const char *to_remove=" \t\r\n")
Definition: utils.h:358
CORE_API std::string join(const std::string &joiner, const Iterator &begin, const Iterator &end, const Transform &transform)
Definition: utils.h:412
std::string name