timemory 3.3.0
Modular C++ Toolkit for Performance Analysis and Logging. Profiling API and Tools for C, C++, CUDA, Fortran, and Python. The C++ template API is essentially a framework to creating tools: it is designed to provide a unifying interface for recording various performance measurements alongside data logging and interfaces to other tools.
argparse.cpp
Go to the documentation of this file.
1// MIT License
2//
3// Copyright (c) 2020, The Regents of the University of California,
4// through Lawrence Berkeley National Laboratory (subject to receipt of any
5// required approvals from the U.S. Dept. of Energy). All rights reserved.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a copy
8// of this software and associated documentation files (the "Software"), to deal
9// in the Software without restriction, including without limitation the rights
10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11// copies of the Software, and to permit persons to whom the Software is
12// furnished to do so, subject to the following conditions:
13//
14// The above copyright notice and this permission notice shall be included in all
15// copies or substantial portions of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23// SOFTWARE.
24
25#ifndef TIMEMORY_UTILITY_ARGPARSE_CPP_
26#define TIMEMORY_UTILITY_ARGPARSE_CPP_
27
29
30#if !defined(TIMEMORY_UTILITY_HEADER_MODE)
32#endif
33
34namespace tim
35{
36namespace argparse
37{
40: base_type()
41{
42 reserve(argc);
43 for(int i = 0; i < argc; ++i)
44 push_back(argv[i]);
45}
46
48argument_vector::argument_vector(int& argc, const char**& argv)
49: base_type()
50{
51 reserve(argc);
52 for(int i = 0; i < argc; ++i)
53 push_back(argv[i]);
54}
55
57argument_vector::argument_vector(int& argc, const char* const*& argv)
58{
59 reserve(argc);
60 for(int i = 0; i < argc; ++i)
61 push_back(argv[i]);
62}
63
65 argument_vector::get_execv(const base_type& _prepend, size_t _beg, size_t _end) const
66{
67 std::stringstream cmdss;
68 // find the end if not specified
69 _end = std::min<size_t>(size(), _end);
70 // determine the number of arguments
71 auto _argc = (_end - _beg) + _prepend.size();
72 // create the new C argument array, add an extra entry at the end which will
73 // always be a null pointer because that is how execv determines the end
74 char** _argv = new char*[_argc + 1];
75
76 // ensure all arguments are null pointers initially
77 for(size_t i = 0; i < _argc + 1; ++i)
78 _argv[i] = nullptr;
79
80 // add the prepend list
81 size_t _idx = 0;
82 for(const auto& itr : _prepend)
83 _argv[_idx++] = helpers::strdup(itr.c_str());
84
85 // copy over the arguments stored internally from the range specified
86 for(auto i = _beg; i < _end; ++i)
87 _argv[_idx++] = helpers::strdup(this->at(i).c_str());
88
89 // add check that last argument really is a nullptr
90 assert(_argv[_argc] == nullptr);
91
92 // create the command string
93 for(size_t i = 0; i < _argc; ++i)
94 cmdss << " " << _argv[i];
95 auto cmd = cmdss.str().substr(1);
96
97 // return a new (int argc, char** argv) and subtract 1 bc nullptr in last entry
98 // does not count as argc
99 return cargs_t(_argc - 1, _argv, cmd);
100}
101
103 argument_vector::get_execv(size_t _beg, size_t _end) const
104{
105 return get_execv(base_type{}, _beg, _end);
106}
107
110{
111 std::stringstream _usage;
112 if(!m_desc.empty())
113 _usage << "[" << m_desc << "] ";
114 _usage << "Usage: " << m_bin;
115
116 std::cerr << _usage.str();
117
118 std::stringstream _sshort_desc;
119 auto _indent = _usage.str().length() + 2;
120 size_t _ncnt = 0;
121 for(auto& a : m_arguments)
122 {
123 std::string name = a.m_names.at(0);
124 if(name.empty() || name.find_first_of('-') > name.find_first_not_of(" -"))
125 continue;
126 // select the first long option
127 for(size_t n = 1; n < a.m_names.size(); ++n)
128 {
129 if(name.find("--") == 0)
130 break;
131 else if(a.m_names.at(n).find("--") == 0)
132 {
133 name = a.m_names.at(n);
134 break;
135 }
136 }
137 if(name.length() > 0)
138 {
139 if(_ncnt++ > 0)
140 _sshort_desc << "\n " << std::setw(_indent) << " " << name;
141 else
142 _sshort_desc << " " << name;
143
144 _sshort_desc << " (";
145 if(a.m_count != argument::Count::ANY)
146 _sshort_desc << "count: " << a.m_count;
147 else if(a.m_min_count != argument::Count::ANY)
148 _sshort_desc << "min: " << a.m_min_count;
149 else if(a.m_max_count != argument::Count::ANY)
150 _sshort_desc << "max: " << a.m_max_count;
151 else
152 _sshort_desc << "count: unlimited";
153 if(!a.m_dtype.empty())
154 _sshort_desc << ", dtype: " << a.m_dtype;
155 else if(a.m_count == 0 ||
156 (a.m_count == argument::Count::ANY && a.m_max_count == 1))
157 _sshort_desc << ", dtype: bool" << a.m_dtype;
158 _sshort_desc << ")";
159 }
160 }
161
162 std::string _short_desc;
163 if(!_sshort_desc.str().empty())
164 {
165 _short_desc.append("[" + _sshort_desc.str());
166 std::stringstream _tmp;
167 _tmp << "\n" << std::setw(_indent) << "]";
168 _short_desc.append(_tmp.str());
169 }
170
171 if(m_positional_arguments.empty())
172 {
173 std::cerr << " " << _short_desc << " " << _extra << std::endl;
174 }
175 else
176 {
177 std::cerr << " " << _short_desc;
178 if(!_short_desc.empty())
179 std::cerr << "\n" << std::setw(_indent - 2) << " ";
180 for(auto& itr : m_positional_arguments)
181 {
182 std::cerr << " " << helpers::ltrim(itr.m_names.at(0), [](int c) -> bool {
183 return c != static_cast<int>('-');
184 });
185 }
186
187 int current = 0;
188 for(auto& v : m_positional_map)
189 {
190 if(v.first != argument::Position::LastArgument)
191 {
192 for(; current < v.first; ++current)
193 std::cerr << " [" << current << "]";
194 std::cerr << " ["
195 << helpers::ltrim(
196 m_arguments[static_cast<size_t>(v.second)].m_names.at(0),
197 [](int c) -> bool { return c != static_cast<int>('-'); })
198 << "]";
199 }
200 else
201 {
202 std::cerr << " ... ["
203 << helpers::ltrim(
204 m_arguments[static_cast<size_t>(v.second)].m_names.at(0),
205 [](int c) -> bool { return c != static_cast<int>('-'); })
206 << "]";
207 }
208 }
209 std::cerr << " " << _extra << std::endl;
210 }
211
212 std::cerr << "\nOptions:" << std::endl;
213 for(auto& a : m_arguments)
214 {
215 std::string name = a.m_names.at(0);
216 for(size_t n = 1; n < a.m_names.size(); ++n)
217 name.append(", " + a.m_names[n]);
218 std::stringstream ss;
219 ss << name;
220 if(a.m_choices.size() > 0)
221 {
222 ss << " [";
223 auto itr = a.m_choices.begin();
224 ss << " " << *itr++;
225 for(; itr != a.m_choices.end(); ++itr)
226 ss << " | " << *itr;
227 ss << " ] ";
228 }
229 std::stringstream prefix;
230 prefix << " " << std::setw(m_width) << std::left << ss.str();
231 std::cerr << std::left << prefix.str();
232
233 auto desc = a.m_desc;
234 if(ss.str().length() >= static_cast<size_t>(m_width))
235 desc = std::string("\n%{NEWLINE}%") + desc;
236
237 {
238 // replace %{INDENT}% with indentation
239 const std::string indent_key = "%{INDENT}%";
240 const auto npos = std::string::npos;
241 auto pos = npos;
242 std::stringstream indent;
243 indent << std::setw(prefix.str().length()) << "";
244 while((pos = desc.find(indent_key)) != npos)
245 desc = desc.replace(pos, indent_key.length(), indent.str());
246 }
247
248 {
249 // replace %{NEWLINE}% with indentation
250 const std::string indent_key = "%{NEWLINE}%";
251 const auto npos = std::string::npos;
252 auto pos = npos;
253 std::stringstream indent;
254 indent << std::setw(m_width + 5) << "";
255 while((pos = desc.find(indent_key)) != npos)
256 desc = desc.replace(pos, indent_key.length(), indent.str());
257 }
258
259 std::cerr << " " << std::setw(m_width) << desc;
260
261 if(a.m_required)
262 std::cerr << " (Required)";
263 std::cerr << std::endl;
264 }
265 std::cerr << '\n';
266}
267
270 const std::string& _delim, int verbose_level)
271{
272 // check for help flag
273 auto help_check = [&](int _argc, char** _argv) {
274 strset_t help_args = { "-h", "--help", "-?" };
275 auto _help_req = (exists("help") ||
276 (_argc > 1 && help_args.find(_argv[1]) != help_args.end()));
277 if(_help_req && !exists("help"))
278 {
279 for(auto hitr : help_args)
280 {
281 auto hstr = hitr.substr(hitr.find_first_not_of('-'));
282 auto itr = m_name_map.find(hstr);
283 if(itr != m_name_map.end())
284 m_arguments[static_cast<size_t>(itr->second)].m_found = true;
285 }
286 }
287 return _help_req;
288 };
289
290 // check for a dash in th command line
291 bool _pdash = false;
292 for(int i = 1; i < *argc; ++i)
293 {
294 if((*argv)[i] == std::string("--"))
295 _pdash = true;
296 }
297
298 // parse the known args and get the remaining argc/argv
299 auto _pargs = parse_known_args(*argc, *argv, _args, _delim, verbose_level);
300 auto _perrc = std::get<0>(_pargs);
301 auto _pargc = std::get<1>(_pargs);
302 auto _pargv = std::get<2>(_pargs);
303
304 // check if help was requested before the dash (if dash exists)
305 if(help_check((_pdash) ? 0 : _pargc, _pargv))
306 return arg_result{ "help requested" };
307
308 // assign the argc and argv
309 *argc = _pargc;
310 *argv = _pargv;
311
312 return _perrc;
313}
314
317 const std::string& _delim, int verbose_level)
318{
319 int _cmdc = argc; // the argc after known args removed
320 char** _cmdv = argv; // the argv after known args removed
321 // _cmdv and argv are same pointer unless delimiter is found
322
323 if(argc > 0)
324 {
325 m_bin = std::string((const char*) argv[0]);
326 _args.push_back(std::string((const char*) argv[0]));
327 }
328
329 for(int i = 1; i < argc; ++i)
330 {
331 std::string _arg = argv[i];
332 if(_arg == _delim)
333 {
334 _cmdc = argc - i;
335 _cmdv = new char*[_cmdc + 1];
336 _cmdv[_cmdc] = nullptr;
337 _cmdv[0] = helpers::strdup(argv[0]);
338 int k = 1;
339 for(int j = i + 1; j < argc; ++j, ++k)
340 _cmdv[k] = helpers::strdup(argv[j]);
341 break;
342 }
343 else
344 {
345 _args.push_back(std::string((const char*) argv[i]));
346 }
347 }
348
349 auto cmd_string = [](int _ac, char** _av) {
350 std::stringstream ss;
351 for(int i = 0; i < _ac; ++i)
352 ss << _av[i] << " ";
353 return ss.str();
354 };
355
356 if((_cmdc > 0 && verbose_level > 0) || verbose_level > 1)
357 std::cerr << "\n";
358
359 if(verbose_level > 1)
360 {
361 std::cerr << "[original]> " << cmd_string(argc, argv) << std::endl;
362 std::cerr << "[cfg-args]> ";
363 for(auto& itr : _args)
364 std::cerr << itr << " ";
365 std::cerr << std::endl;
366 }
367
368 if(_cmdc > 0 && verbose_level > 0)
369 std::cerr << "[command]> " << cmd_string(_cmdc, _cmdv) << "\n\n";
370
371 return known_args_t{ parse(_args, verbose_level), _cmdc, _cmdv };
372}
373
375 argument_parser::parse(const std::vector<std::string>& _args, int verbose_level)
376{
377 if(verbose_level > 0)
378 {
379 std::cerr << "[argparse::parse]> parsing '";
380 for(const auto& itr : _args)
381 std::cerr << itr << " ";
382 std::cerr << "'" << '\n';
383 }
384
385 for(auto& a : m_arguments)
386 a.m_callback(a.m_default);
387 for(auto& a : m_positional_arguments)
388 a.m_callback(a.m_default);
389
390 using argmap_t = std::map<std::string, argument*>;
391
392 argmap_t m_arg_map = {};
393 arg_result err;
394 int argc = _args.size();
395 // the set of options which use a single leading dash but are longer than
396 // one character, e.g. -LS ...
397 std::set<std::string> long_short_opts;
398 if(_args.size() > 1)
399 {
400 auto is_leading_dash = [](int c) -> bool { return c != static_cast<int>('-'); };
401 // build name map
402 for(auto& a : m_arguments)
403 {
404 for(auto& n : a.m_names)
405 {
406 auto nleading_dash = helpers::lcount(n, is_leading_dash);
407 std::string name = helpers::ltrim(n, is_leading_dash);
408 if(name.empty())
409 continue;
410 if(m_name_map.find(name) != m_name_map.end())
411 return arg_result("Duplicate of argument name: " + n);
412 m_name_map[name] = a.m_index;
413 m_arg_map[name] = &a;
414 if(nleading_dash == 1 && name.length() > 1)
415 long_short_opts.insert(name);
416 }
417 if(a.m_position >= 0 || a.m_position == argument::Position::LastArgument)
418 m_positional_map.at(a.m_position) = a.m_index;
419 }
420
421 m_bin = _args.at(0);
422
423 // parse
424 std::string current_arg;
425 size_t arg_len;
426 for(int argv_index = 1; argv_index < argc; ++argv_index)
427 {
428 current_arg = _args.at(argv_index);
429 arg_len = current_arg.length();
430 if(arg_len == 0)
431 continue;
432 if(argv_index == argc - 1 &&
433 m_positional_map.find(argument::Position::LastArgument) !=
434 m_positional_map.end())
435 {
436 err = end_argument();
437 arg_result b = err;
438 err = add_value(current_arg, argument::Position::LastArgument);
439 if(b)
440 return b;
441 // return (m_error_func(*this, b), b);
442 if(err)
443 return (m_error_func(*this, err), err);
444 continue;
445 }
446
447 // count number of leading dashes
448 auto nleading_dash = helpers::lcount(current_arg, is_leading_dash);
449 // ignores the case if the arg is just a '-'
450 // look for -a (short) or --arg (long) args
451 bool is_arg = (nleading_dash > 0 && arg_len > 1 && arg_len != nleading_dash)
452 ? true
453 : false;
454
455 if(is_arg && !helpers::is_numeric(current_arg))
456 {
457 err = end_argument();
458 if(err)
459 return (m_error_func(*this, err), err);
460
461 auto name = current_arg.substr(nleading_dash);
462 auto islong = (nleading_dash > 1 || long_short_opts.count(name) > 0);
463 err = begin_argument(name, islong, argv_index);
464 if(err)
465 return (m_error_func(*this, err), err);
466 }
467 else if(current_arg.length() > 0)
468 {
469 // argument value
470 err = add_value(current_arg, argv_index);
471 if(err)
472 return (m_error_func(*this, err), err);
473 }
474 }
475 }
476
477 // return the help
478 if(m_help_enabled && exists("help"))
479 return arg_result("help requested");
480
481 err = end_argument();
482 if(err)
483 return (m_error_func(*this, err), err);
484
485 // check requirements
486 for(auto& a : m_arguments)
487 {
488 if(a.m_required && !a.m_found)
489 {
490 return arg_result("Required argument not found: " + a.m_names.at(0));
491 }
492 if(a.m_position >= 0 && argc >= a.m_position && !a.m_found)
493 {
494 return arg_result("argument " + a.m_names.at(0) + " expected in position " +
495 std::to_string(a.m_position));
496 }
497 }
498
499 // check requirements
500 for(auto& a : m_positional_arguments)
501 {
502 if(a.m_required && !a.m_found)
503 return arg_result("Required argument not found: " + a.m_names.at(0));
504 }
505
506 // check all the counts have been satisfied
507 for(auto& a : m_arguments)
508 {
509 if(a.m_found && a.m_default == nullptr)
510 {
511 auto cnt_err = check_count(a);
512 if(cnt_err)
513 return cnt_err;
514 }
515 }
516
517 // execute the global actions
518 for(auto& itr : m_actions)
519 {
520 if(itr.first(*this))
521 itr.second(*this);
522 }
523
524 // execute the argument-specific actions
525 for(auto& itr : m_arg_map)
526 {
527 if(exists(itr.first))
528 itr.second->execute_actions(*this);
529 }
530
531 return arg_result{};
532}
533
535 argument_parser::begin_argument(const std::string& arg, bool longarg, int position)
536{
537 auto it = m_positional_map.find(position);
538 if(it != m_positional_map.end())
539 {
540 arg_result err = end_argument();
541 argument& a = m_arguments[static_cast<size_t>(it->second)];
542 a.m_values.push_back(arg);
543 a.m_found = true;
544 return err;
545 }
546 if(m_current != -1)
547 {
548 return arg_result("Current argument left open");
549 }
550 size_t name_end = helpers::find_punct(arg);
551 std::string arg_name = arg.substr(0, name_end);
552 if(longarg)
553 {
554 int equal_pos = helpers::find_equiv(arg);
555 auto nmf = m_name_map.find(arg_name);
556 if(nmf == m_name_map.end())
557 {
558 arg_name = arg.substr(0, equal_pos);
559 nmf = m_name_map.find(arg_name);
560 }
561 if(nmf == m_name_map.end())
562 {
563 return arg_result("Unrecognized command line option '" + arg_name + "'");
564 }
565 m_current = nmf->second;
566 m_arguments[static_cast<size_t>(nmf->second)].m_found = true;
567 if(equal_pos == 0 || (equal_pos < 0 && arg_name.length() < arg.length()))
568 {
569 // malformed argument
570 return arg_result("Malformed argument: " + arg);
571 }
572 else if(equal_pos > 0)
573 {
574 std::string arg_value = arg.substr(name_end + 1);
575 add_value(arg_value, position);
576 }
577 }
578 else
579 {
580 arg_result r;
581 if(arg_name.length() == 1)
582 {
583 return begin_argument(arg, true, position);
584 }
585 else
586 {
587 for(char& c : arg_name)
588 {
589 r = begin_argument(std::string(1, c), true, position);
590 if(r)
591 {
592 return r;
593 }
594 r = end_argument();
595 if(r)
596 {
597 return r;
598 }
599 }
600 }
601 }
602 return arg_result{};
603}
604
605TIMEMORY_UTILITY_INLINE argument_parser::arg_result
606 argument_parser::add_value(const std::string& value, int location)
607{
608 auto unnamed = [&]() {
609 auto itr = m_positional_map.find(location);
610 if(itr != m_positional_map.end())
611 {
612 argument& a = m_arguments[static_cast<size_t>(itr->second)];
613 a.m_values.push_back(value);
614 a.m_found = true;
615 }
616 else
617 {
618 auto idx = m_positional_values.size();
619 m_positional_values.emplace(idx, value);
620 if(idx < m_positional_arguments.size())
621 {
622 auto& a = m_positional_arguments.at(idx);
623 a.m_found = true;
624 auto err = a.check_choice(value);
625 if(err)
626 return err;
627 a.m_values.push_back(value);
628 a.execute_actions(*this);
629 }
630 }
631 return arg_result{};
632 };
633
634 if(m_current >= 0)
635 {
636 arg_result err;
637 size_t c = static_cast<size_t>(m_current);
639 argument& a = m_arguments[static_cast<size_t>(m_current)];
640
641 err = a.check_choice(value);
642 if(err)
643 return err;
644
645 auto num_values = [&]() { return static_cast<int>(a.m_values.size()); };
646
647 // check {m_count, m_max_count} > COUNT::ANY && m_values.size() >= {value}
648 if((a.m_count >= 0 && num_values() >= a.m_count) ||
649 (a.m_max_count >= 0 && num_values() >= a.m_max_count))
650 {
651 err = end_argument();
652 if(err)
653 return err;
654 return unnamed();
655 }
656
657 a.m_values.push_back(value);
658
659 // check {m_count, m_max_count} > COUNT::ANY && m_values.size() >= {value}
660 if((a.m_count >= 0 && num_values() >= a.m_count) ||
661 (a.m_max_count >= 0 && num_values() >= a.m_max_count))
662 {
663 err = end_argument();
664 if(err)
665 return err;
666 }
667 return arg_result{};
668 }
669
670 return unnamed();
671}
672
673TIMEMORY_UTILITY_INLINE argument_parser::arg_result
674 argument_parser::end_argument()
675{
676 if(m_current >= 0)
677 {
678 argument& a = m_arguments[static_cast<size_t>(m_current)];
679 m_current = -1;
680 if(static_cast<int>(a.m_values.size()) < a.m_count)
681 return arg_result("Too few arguments given for " + a.m_names.at(0));
682 if(a.m_max_count >= 0)
683 {
684 if(static_cast<int>(a.m_values.size()) > a.m_max_count)
685 return arg_result("Too many arguments given for " + a.m_names.at(0));
686 }
687 else if(a.m_count >= 0)
688 {
689 if(static_cast<int>(a.m_values.size()) > a.m_count)
690 return arg_result("Too many arguments given for " + a.m_names.at(0));
691 }
692 }
693 return arg_result{};
694}
695
697 operator<<(std::ostream& os, const argument_parser::arg_result& r)
698{
699 os << r.what();
700 return os;
701}
702
703} // namespace argparse
704} // namespace tim
705
706#endif
TIMEMORY_UTILITY_INLINE std::ostream & operator<<(std::ostream &os, const argument_parser::arg_result &r)
Definition: argparse.cpp:697
Definition: kokkosp.cpp:39
std::array< char *, 4 > _args
char ** argv
Definition: config.cpp:55
tim::mpl::apply< std::string > string
Definition: macros.hpp:53
size_t pos
Definition: config.cpp:102
const std::string std::ostream * os
_args at(0)
void consume_parameters(ArgsT &&...)
Definition: types.hpp:285
const std::string & what() const
Definition: argparse.hpp:351
bool exists(const std::string &name) const
Returns whether or not an option was found in the arguments. Only useful after a call to parse or par...
Definition: argparse.hpp:912
known_args_t parse_known_args(int argc, char **argv, const std::string &_delim="--", int verbose_level=0)
Basic variant of parse_known_args which does not replace argc/argv and does not provide an array of s...
Definition: argparse.hpp:765
std::tuple< arg_result, int, char ** > known_args_t
Definition: argparse.hpp:333
arg_result parse(int argc, char **argv, int verbose_level=0)
Definition: argparse.hpp:847
void print_help(const std::string &_extra="")
Definition: argparse.cpp:109
std::set< std::string > strset_t
Definition: argparse.hpp:335
std::vector< std::string > strvec_t
Definition: argparse.hpp:334
argument_vector(Args &&... args)
Definition: argparse.hpp:300
cargs_t get_execv(const base_type &_prepend, size_t _beg=0, size_t _end=std::numeric_limits< size_t >::max()) const
Definition: argparse.cpp:65
std::vector< std::string > base_type
Definition: argparse.hpp:296
char * strdup(const char *s)
Definition: timemory_c.c:41
#define TIMEMORY_UTILITY_INLINE
Definition: macros.hpp:308