blob: 8aa17bd63b3ce8906716d07018edcec7fafcff03 [file] [log] [blame]
Jan Eilers7e989832020-06-19 11:47:21 +01001/*
2
Jim Flynn357add22023-04-10 23:26:40 +01003Copyright (c) 2014-2022 Jarryd Beck
Jim Flynn6217c3d2022-06-14 10:58:23 +01004SPDX-License-Identifier: MIT
5
Jan Eilers7e989832020-06-19 11:47:21 +01006Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in
14all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22THE SOFTWARE.
23
24*/
25
Jim Flynn357add22023-04-10 23:26:40 +010026// vim: ts=2:sw=2:expandtab
27
Jan Eilers7e989832020-06-19 11:47:21 +010028#ifndef CXXOPTS_HPP_INCLUDED
29#define CXXOPTS_HPP_INCLUDED
30
Jan Eilers7e989832020-06-19 11:47:21 +010031#include <cstring>
32#include <exception>
Jan Eilers7e989832020-06-19 11:47:21 +010033#include <limits>
Jim Flynn357add22023-04-10 23:26:40 +010034#include <initializer_list>
Jan Eilers7e989832020-06-19 11:47:21 +010035#include <map>
36#include <memory>
Jan Eilers7e989832020-06-19 11:47:21 +010037#include <sstream>
38#include <string>
39#include <unordered_map>
40#include <unordered_set>
41#include <utility>
42#include <vector>
Jim Flynn357add22023-04-10 23:26:40 +010043#include <algorithm>
44#include <locale>
Jan Eilers7e989832020-06-19 11:47:21 +010045
Jim Flynn357add22023-04-10 23:26:40 +010046#ifdef CXXOPTS_NO_EXCEPTIONS
47#include <iostream>
48#endif
49
50#if defined(__GNUC__) && !defined(__clang__)
51# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49
52# define CXXOPTS_NO_REGEX true
53# endif
54#endif
55#if defined(_MSC_VER) && !defined(__clang__)
56#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern
57#define CXXOPTS_LINKONCE __declspec(selectany) extern
58#else
59#define CXXOPTS_LINKONCE_CONST
60#define CXXOPTS_LINKONCE
61#endif
62
63#ifndef CXXOPTS_NO_REGEX
64# include <regex>
65#endif // CXXOPTS_NO_REGEX
66
67// Nonstandard before C++17, which is coincidentally what we also need for <optional>
68#ifdef __has_include
69# if __has_include(<optional>)
70# include <optional>
71# ifdef __cpp_lib_optional
72# define CXXOPTS_HAS_OPTIONAL
73# endif
74# endif
Jan Eilers7e989832020-06-19 11:47:21 +010075#endif
76
Matthew Sloyan84dc8432020-10-06 16:06:07 +010077#if __cplusplus >= 201603L
78#define CXXOPTS_NODISCARD [[nodiscard]]
79#else
80#define CXXOPTS_NODISCARD
81#endif
82
Jan Eilers7e989832020-06-19 11:47:21 +010083#ifndef CXXOPTS_VECTOR_DELIMITER
84#define CXXOPTS_VECTOR_DELIMITER ','
85#endif
86
Matthew Sloyan84dc8432020-10-06 16:06:07 +010087#define CXXOPTS__VERSION_MAJOR 3
Jim Flynn357add22023-04-10 23:26:40 +010088#define CXXOPTS__VERSION_MINOR 1
89#define CXXOPTS__VERSION_PATCH 1
Jan Eilers7e989832020-06-19 11:47:21 +010090
Jim Flynn357add22023-04-10 23:26:40 +010091#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6
92 #define CXXOPTS_NULL_DEREF_IGNORE
93#endif
94
95#if defined(__GNUC__)
96#define DO_PRAGMA(x) _Pragma(#x)
97#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push)
98#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop)
99#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x)
100#else
101// define other compilers here if needed
102#define CXXOPTS_DIAGNOSTIC_PUSH
103#define CXXOPTS_DIAGNOSTIC_POP
104#define CXXOPTS_IGNORE_WARNING(x)
105#endif
106
107#ifdef CXXOPTS_NO_RTTI
108#define CXXOPTS_RTTI_CAST static_cast
109#else
110#define CXXOPTS_RTTI_CAST dynamic_cast
111#endif
112
113namespace cxxopts {
114static constexpr struct {
115 uint8_t major, minor, patch;
116} version = {
117 CXXOPTS__VERSION_MAJOR,
118 CXXOPTS__VERSION_MINOR,
119 CXXOPTS__VERSION_PATCH
120};
Jan Eilers7e989832020-06-19 11:47:21 +0100121} // namespace cxxopts
122
123//when we ask cxxopts to use Unicode, help strings are processed using ICU,
124//which results in the correct lengths being computed for strings when they
125//are formatted for the help output
126//it is necessary to make sure that <unicode/unistr.h> can be found by the
127//compiler, and that icu-uc is linked in to the binary.
128
129#ifdef CXXOPTS_USE_UNICODE
130#include <unicode/unistr.h>
131
Jim Flynn357add22023-04-10 23:26:40 +0100132namespace cxxopts {
133
134using String = icu::UnicodeString;
135
136inline
137String
138toLocalString(std::string s)
Jan Eilers7e989832020-06-19 11:47:21 +0100139{
Jim Flynn357add22023-04-10 23:26:40 +0100140 return icu::UnicodeString::fromUTF8(std::move(s));
141}
Jan Eilers7e989832020-06-19 11:47:21 +0100142
Jim Flynn357add22023-04-10 23:26:40 +0100143// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:
144// warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor
145CXXOPTS_DIAGNOSTIC_PUSH
146CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor")
147// This will be ignored under other compilers like LLVM clang.
148class UnicodeStringIterator
149{
150 public:
151
152 using iterator_category = std::forward_iterator_tag;
153 using value_type = int32_t;
154 using difference_type = std::ptrdiff_t;
155 using pointer = value_type*;
156 using reference = value_type&;
157
158 UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos)
159 : s(string)
160 , i(pos)
Jan Eilers7e989832020-06-19 11:47:21 +0100161 {
Jan Eilers7e989832020-06-19 11:47:21 +0100162 }
163
Jim Flynn357add22023-04-10 23:26:40 +0100164 value_type
165 operator*() const
Jan Eilers7e989832020-06-19 11:47:21 +0100166 {
Jim Flynn357add22023-04-10 23:26:40 +0100167 return s->char32At(i);
Jan Eilers7e989832020-06-19 11:47:21 +0100168 }
169
Jan Eilers7e989832020-06-19 11:47:21 +0100170 bool
Jim Flynn357add22023-04-10 23:26:40 +0100171 operator==(const UnicodeStringIterator& rhs) const
Jan Eilers7e989832020-06-19 11:47:21 +0100172 {
Jim Flynn357add22023-04-10 23:26:40 +0100173 return s == rhs.s && i == rhs.i;
Jan Eilers7e989832020-06-19 11:47:21 +0100174 }
Jan Eilers7e989832020-06-19 11:47:21 +0100175
Jim Flynn357add22023-04-10 23:26:40 +0100176 bool
177 operator!=(const UnicodeStringIterator& rhs) const
178 {
179 return !(*this == rhs);
180 }
181
182 UnicodeStringIterator&
183 operator++()
184 {
185 ++i;
186 return *this;
187 }
188
189 UnicodeStringIterator
190 operator+(int32_t v)
191 {
192 return UnicodeStringIterator(s, i + v);
193 }
194
195 private:
196 const icu::UnicodeString* s;
197 int32_t i;
198};
199CXXOPTS_DIAGNOSTIC_POP
200
201inline
202String&
203stringAppend(String&s, String a)
Jan Eilers7e989832020-06-19 11:47:21 +0100204{
Jim Flynn357add22023-04-10 23:26:40 +0100205 return s.append(std::move(a));
206}
207
208inline
209String&
210stringAppend(String& s, std::size_t n, UChar32 c)
211{
212 for (std::size_t i = 0; i != n; ++i)
Jan Eilers7e989832020-06-19 11:47:21 +0100213 {
Jim Flynn357add22023-04-10 23:26:40 +0100214 s.append(c);
Jan Eilers7e989832020-06-19 11:47:21 +0100215 }
216
Jim Flynn357add22023-04-10 23:26:40 +0100217 return s;
Jan Eilers7e989832020-06-19 11:47:21 +0100218}
219
Jim Flynn357add22023-04-10 23:26:40 +0100220template <typename Iterator>
221String&
222stringAppend(String& s, Iterator begin, Iterator end)
223{
224 while (begin != end)
225 {
226 s.append(*begin);
227 ++begin;
228 }
229
230 return s;
231}
232
233inline
234size_t
235stringLength(const String& s)
236{
237 return static_cast<size_t>(s.length());
238}
239
240inline
241std::string
242toUTF8String(const String& s)
243{
244 std::string result;
245 s.toUTF8String(result);
246
247 return result;
248}
249
250inline
251bool
252empty(const String& s)
253{
254 return s.isEmpty();
255}
256
257} // namespace cxxopts
258
259namespace std {
260
261inline
262cxxopts::UnicodeStringIterator
263begin(const icu::UnicodeString& s)
264{
265 return cxxopts::UnicodeStringIterator(&s, 0);
266}
267
268inline
269cxxopts::UnicodeStringIterator
270end(const icu::UnicodeString& s)
271{
272 return cxxopts::UnicodeStringIterator(&s, s.length());
273}
274
275} // namespace std
276
Jan Eilers7e989832020-06-19 11:47:21 +0100277//ifdef CXXOPTS_USE_UNICODE
278#else
279
Jim Flynn357add22023-04-10 23:26:40 +0100280namespace cxxopts {
281
282using String = std::string;
283
284template <typename T>
285T
286toLocalString(T&& t)
Jan Eilers7e989832020-06-19 11:47:21 +0100287{
Jim Flynn357add22023-04-10 23:26:40 +0100288 return std::forward<T>(t);
289}
Jan Eilers7e989832020-06-19 11:47:21 +0100290
Jim Flynn357add22023-04-10 23:26:40 +0100291inline
292std::size_t
293stringLength(const String& s)
294{
295 return s.length();
296}
Jan Eilers7e989832020-06-19 11:47:21 +0100297
Jim Flynn357add22023-04-10 23:26:40 +0100298inline
299String&
300stringAppend(String&s, const String& a)
301{
302 return s.append(a);
303}
Jan Eilers7e989832020-06-19 11:47:21 +0100304
Jim Flynn357add22023-04-10 23:26:40 +0100305inline
306String&
307stringAppend(String& s, std::size_t n, char c)
308{
309 return s.append(n, c);
310}
Jan Eilers7e989832020-06-19 11:47:21 +0100311
Jim Flynn357add22023-04-10 23:26:40 +0100312template <typename Iterator>
313String&
314stringAppend(String& s, Iterator begin, Iterator end)
315{
316 return s.append(begin, end);
317}
Jan Eilers7e989832020-06-19 11:47:21 +0100318
Jim Flynn357add22023-04-10 23:26:40 +0100319template <typename T>
320std::string
321toUTF8String(T&& t)
322{
323 return std::forward<T>(t);
324}
Jan Eilers7e989832020-06-19 11:47:21 +0100325
Jim Flynn357add22023-04-10 23:26:40 +0100326inline
327bool
328empty(const std::string& s)
329{
330 return s.empty();
331}
Jan Eilers7e989832020-06-19 11:47:21 +0100332
Jan Eilers7e989832020-06-19 11:47:21 +0100333} // namespace cxxopts
334
335//ifdef CXXOPTS_USE_UNICODE
336#endif
337
Jim Flynn357add22023-04-10 23:26:40 +0100338namespace cxxopts {
339
340namespace {
341CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'");
342CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'");
343} // namespace
344
345// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we
346// want to silence it: warning: base class 'class
347// std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual
348// destructor This will be ignored under other compilers like LLVM clang.
349CXXOPTS_DIAGNOSTIC_PUSH
350CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor")
351
352// some older versions of GCC warn under this warning
353CXXOPTS_IGNORE_WARNING("-Weffc++")
354class Value : public std::enable_shared_from_this<Value>
Jan Eilers7e989832020-06-19 11:47:21 +0100355{
Jim Flynn357add22023-04-10 23:26:40 +0100356 public:
357
358 virtual ~Value() = default;
359
360 virtual
361 std::shared_ptr<Value>
362 clone() const = 0;
363
364 virtual void
365 parse(const std::string& text) const = 0;
366
367 virtual void
368 parse() const = 0;
369
370 virtual bool
371 has_default() const = 0;
372
373 virtual bool
374 is_container() const = 0;
375
376 virtual bool
377 has_implicit() const = 0;
378
379 virtual std::string
380 get_default_value() const = 0;
381
382 virtual std::string
383 get_implicit_value() const = 0;
384
385 virtual std::shared_ptr<Value>
386 default_value(const std::string& value) = 0;
387
388 virtual std::shared_ptr<Value>
389 implicit_value(const std::string& value) = 0;
390
391 virtual std::shared_ptr<Value>
392 no_implicit_value() = 0;
393
394 virtual bool
395 is_boolean() const = 0;
396};
397
398CXXOPTS_DIAGNOSTIC_POP
399
400namespace exceptions {
401
402class exception : public std::exception
403{
404 public:
405 explicit exception(std::string message)
406 : m_message(std::move(message))
Jan Eilers7e989832020-06-19 11:47:21 +0100407 {
Jim Flynn357add22023-04-10 23:26:40 +0100408 }
Jan Eilers7e989832020-06-19 11:47:21 +0100409
Jim Flynn357add22023-04-10 23:26:40 +0100410 CXXOPTS_NODISCARD
411 const char*
412 what() const noexcept override
Jan Eilers7e989832020-06-19 11:47:21 +0100413 {
Jim Flynn357add22023-04-10 23:26:40 +0100414 return m_message.c_str();
415 }
Jan Eilers7e989832020-06-19 11:47:21 +0100416
Jim Flynn357add22023-04-10 23:26:40 +0100417 private:
418 std::string m_message;
419};
Jan Eilers7e989832020-06-19 11:47:21 +0100420
Jim Flynn357add22023-04-10 23:26:40 +0100421class specification : public exception
422{
423 public:
Jan Eilers7e989832020-06-19 11:47:21 +0100424
Jim Flynn357add22023-04-10 23:26:40 +0100425 explicit specification(const std::string& message)
426 : exception(message)
Jan Eilers7e989832020-06-19 11:47:21 +0100427 {
Jim Flynn357add22023-04-10 23:26:40 +0100428 }
429};
Jan Eilers7e989832020-06-19 11:47:21 +0100430
Jim Flynn357add22023-04-10 23:26:40 +0100431class parsing : public exception
432{
433 public:
434 explicit parsing(const std::string& message)
435 : exception(message)
Jan Eilers7e989832020-06-19 11:47:21 +0100436 {
Jim Flynn357add22023-04-10 23:26:40 +0100437 }
438};
Jan Eilers7e989832020-06-19 11:47:21 +0100439
Jim Flynn357add22023-04-10 23:26:40 +0100440class option_already_exists : public specification
441{
442 public:
443 explicit option_already_exists(const std::string& option)
444 : specification("Option " + LQUOTE + option + RQUOTE + " already exists")
Jan Eilers7e989832020-06-19 11:47:21 +0100445 {
Jim Flynn357add22023-04-10 23:26:40 +0100446 }
447};
Jan Eilers7e989832020-06-19 11:47:21 +0100448
Jim Flynn357add22023-04-10 23:26:40 +0100449class invalid_option_format : public specification
450{
451 public:
452 explicit invalid_option_format(const std::string& format)
453 : specification("Invalid option format " + LQUOTE + format + RQUOTE)
Jan Eilers7e989832020-06-19 11:47:21 +0100454 {
Jim Flynn357add22023-04-10 23:26:40 +0100455 }
456};
Jan Eilers7e989832020-06-19 11:47:21 +0100457
Jim Flynn357add22023-04-10 23:26:40 +0100458class invalid_option_syntax : public parsing {
459 public:
460 explicit invalid_option_syntax(const std::string& text)
461 : parsing("Argument " + LQUOTE + text + RQUOTE +
462 " starts with a - but has incorrect syntax")
Jan Eilers7e989832020-06-19 11:47:21 +0100463 {
Jim Flynn357add22023-04-10 23:26:40 +0100464 }
465};
Jan Eilers7e989832020-06-19 11:47:21 +0100466
Jim Flynn357add22023-04-10 23:26:40 +0100467class no_such_option : public parsing
468{
469 public:
470 explicit no_such_option(const std::string& option)
471 : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist")
Jan Eilers7e989832020-06-19 11:47:21 +0100472 {
Jim Flynn357add22023-04-10 23:26:40 +0100473 }
474};
Jan Eilers7e989832020-06-19 11:47:21 +0100475
Jim Flynn357add22023-04-10 23:26:40 +0100476class missing_argument : public parsing
477{
478 public:
479 explicit missing_argument(const std::string& option)
480 : parsing(
481 "Option " + LQUOTE + option + RQUOTE + " is missing an argument"
Jan Eilers7e989832020-06-19 11:47:21 +0100482 )
Jan Eilers7e989832020-06-19 11:47:21 +0100483 {
Jim Flynn357add22023-04-10 23:26:40 +0100484 }
485};
Jan Eilers7e989832020-06-19 11:47:21 +0100486
Jim Flynn357add22023-04-10 23:26:40 +0100487class option_requires_argument : public parsing
488{
489 public:
490 explicit option_requires_argument(const std::string& option)
491 : parsing(
492 "Option " + LQUOTE + option + RQUOTE + " requires an argument"
Jan Eilers7e989832020-06-19 11:47:21 +0100493 )
Jan Eilers7e989832020-06-19 11:47:21 +0100494 {
Jim Flynn357add22023-04-10 23:26:40 +0100495 }
496};
Jan Eilers7e989832020-06-19 11:47:21 +0100497
Jim Flynn357add22023-04-10 23:26:40 +0100498class gratuitous_argument_for_option : public parsing
499{
500 public:
501 gratuitous_argument_for_option
502 (
503 const std::string& option,
504 const std::string& arg
505 )
506 : parsing(
507 "Option " + LQUOTE + option + RQUOTE +
508 " does not take an argument, but argument " +
509 LQUOTE + arg + RQUOTE + " given"
510 )
Jan Eilers7e989832020-06-19 11:47:21 +0100511 {
Jim Flynn357add22023-04-10 23:26:40 +0100512 }
513};
514
515class requested_option_not_present : public parsing
516{
517 public:
518 explicit requested_option_not_present(const std::string& option)
519 : parsing("Option " + LQUOTE + option + RQUOTE + " not present")
520 {
521 }
522};
523
524class option_has_no_value : public exception
525{
526 public:
527 explicit option_has_no_value(const std::string& option)
528 : exception(
529 !option.empty() ?
530 ("Option " + LQUOTE + option + RQUOTE + " has no value") :
531 "Option has no value")
532 {
533 }
534};
535
536class incorrect_argument_type : public parsing
537{
538 public:
539 explicit incorrect_argument_type
540 (
541 const std::string& arg
542 )
543 : parsing(
544 "Argument " + LQUOTE + arg + RQUOTE + " failed to parse"
545 )
546 {
547 }
548};
549
550} // namespace exceptions
551
552
553template <typename T>
554void throw_or_mimic(const std::string& text)
555{
556 static_assert(std::is_base_of<std::exception, T>::value,
557 "throw_or_mimic only works on std::exception and "
558 "deriving classes");
Jan Eilers7e989832020-06-19 11:47:21 +0100559
560#ifndef CXXOPTS_NO_EXCEPTIONS
Jim Flynn357add22023-04-10 23:26:40 +0100561 // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw
562 throw T{text};
Jan Eilers7e989832020-06-19 11:47:21 +0100563#else
Jim Flynn357add22023-04-10 23:26:40 +0100564 // Otherwise manually instantiate the exception, print what() to stderr,
565 // and exit
566 T exception{text};
567 std::cerr << exception.what() << std::endl;
568 std::exit(EXIT_FAILURE);
Jan Eilers7e989832020-06-19 11:47:21 +0100569#endif
Jim Flynn357add22023-04-10 23:26:40 +0100570}
571
572using OptionNames = std::vector<std::string>;
573
574namespace values {
575
576namespace parser_tool {
577
578struct IntegerDesc
579{
580 std::string negative = "";
581 std::string base = "";
582 std::string value = "";
583};
584struct ArguDesc {
585 std::string arg_name = "";
586 bool grouping = false;
587 bool set_value = false;
588 std::string value = "";
589};
590
591#ifdef CXXOPTS_NO_REGEX
592inline IntegerDesc SplitInteger(const std::string &text)
593{
594 if (text.empty())
595 {
596 throw_or_mimic<exceptions::incorrect_argument_type>(text);
597 }
598 IntegerDesc desc;
599 const char *pdata = text.c_str();
600 if (*pdata == '-')
601 {
602 pdata += 1;
603 desc.negative = "-";
604 }
605 if (strncmp(pdata, "0x", 2) == 0)
606 {
607 pdata += 2;
608 desc.base = "0x";
609 }
610 if (*pdata != '\0')
611 {
612 desc.value = std::string(pdata);
613 }
614 else
615 {
616 throw_or_mimic<exceptions::incorrect_argument_type>(text);
617 }
618 return desc;
619}
620
621inline bool IsTrueText(const std::string &text)
622{
623 const char *pdata = text.c_str();
624 if (*pdata == 't' || *pdata == 'T')
625 {
626 pdata += 1;
627 if (strncmp(pdata, "rue\0", 4) == 0)
628 {
629 return true;
630 }
631 }
632 else if (strncmp(pdata, "1\0", 2) == 0)
633 {
634 return true;
635 }
636 return false;
637}
638
639inline bool IsFalseText(const std::string &text)
640{
641 const char *pdata = text.c_str();
642 if (*pdata == 'f' || *pdata == 'F')
643 {
644 pdata += 1;
645 if (strncmp(pdata, "alse\0", 5) == 0)
646 {
647 return true;
648 }
649 }
650 else if (strncmp(pdata, "0\0", 2) == 0)
651 {
652 return true;
653 }
654 return false;
655}
656
657inline OptionNames split_option_names(const std::string &text)
658{
659 OptionNames split_names;
660
661 std::string::size_type token_start_pos = 0;
662 auto length = text.length();
663
664 if (length == 0)
665 {
666 throw_or_mimic<exceptions::invalid_option_format>(text);
Jan Eilers7e989832020-06-19 11:47:21 +0100667 }
668
Jim Flynn357add22023-04-10 23:26:40 +0100669 while (token_start_pos < length) {
670 const auto &npos = std::string::npos;
671 auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos);
672 if (next_non_space_pos == npos) {
673 throw_or_mimic<exceptions::invalid_option_format>(text);
674 }
675 token_start_pos = next_non_space_pos;
676 auto next_delimiter_pos = text.find(',', token_start_pos);
677 if (next_delimiter_pos == token_start_pos) {
678 throw_or_mimic<exceptions::invalid_option_format>(text);
679 }
680 if (next_delimiter_pos == npos) {
681 next_delimiter_pos = length;
682 }
683 auto token_length = next_delimiter_pos - token_start_pos;
684 // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/
685 {
686 const char* option_name_valid_chars =
687 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
688 "abcdefghijklmnopqrstuvwxyz"
689 "0123456789"
690 "_-.?";
691
692 if (!std::isalnum(text[token_start_pos], std::locale::classic()) ||
693 text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) {
694 throw_or_mimic<exceptions::invalid_option_format>(text);
695 }
696 }
697 split_names.emplace_back(text.substr(token_start_pos, token_length));
698 token_start_pos = next_delimiter_pos + 1;
699 }
700 return split_names;
701}
702
703inline ArguDesc ParseArgument(const char *arg, bool &matched)
704{
705 ArguDesc argu_desc;
706 const char *pdata = arg;
707 matched = false;
708 if (strncmp(pdata, "--", 2) == 0)
Jan Eilers7e989832020-06-19 11:47:21 +0100709 {
Jim Flynn357add22023-04-10 23:26:40 +0100710 pdata += 2;
711 if (isalnum(*pdata, std::locale::classic()))
Jan Eilers7e989832020-06-19 11:47:21 +0100712 {
Jim Flynn357add22023-04-10 23:26:40 +0100713 argu_desc.arg_name.push_back(*pdata);
714 pdata += 1;
715 while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_')
Jan Eilers7e989832020-06-19 11:47:21 +0100716 {
Jim Flynn357add22023-04-10 23:26:40 +0100717 argu_desc.arg_name.push_back(*pdata);
718 pdata += 1;
719 }
720 if (argu_desc.arg_name.length() > 1)
721 {
722 if (*pdata == '=')
Jan Eilers7e989832020-06-19 11:47:21 +0100723 {
Jim Flynn357add22023-04-10 23:26:40 +0100724 argu_desc.set_value = true;
725 pdata += 1;
726 if (*pdata != '\0')
Jan Eilers7e989832020-06-19 11:47:21 +0100727 {
Jim Flynn357add22023-04-10 23:26:40 +0100728 argu_desc.value = std::string(pdata);
Jan Eilers7e989832020-06-19 11:47:21 +0100729 }
Jim Flynn357add22023-04-10 23:26:40 +0100730 matched = true;
Jan Eilers7e989832020-06-19 11:47:21 +0100731 }
Jim Flynn357add22023-04-10 23:26:40 +0100732 else if (*pdata == '\0')
Jan Eilers7e989832020-06-19 11:47:21 +0100733 {
Jim Flynn357add22023-04-10 23:26:40 +0100734 matched = true;
Jan Eilers7e989832020-06-19 11:47:21 +0100735 }
Jan Eilers7e989832020-06-19 11:47:21 +0100736 }
Jim Flynn357add22023-04-10 23:26:40 +0100737 }
738 }
739 else if (strncmp(pdata, "-", 1) == 0)
740 {
741 pdata += 1;
742 argu_desc.grouping = true;
743 while (isalnum(*pdata, std::locale::classic()))
744 {
745 argu_desc.arg_name.push_back(*pdata);
746 pdata += 1;
747 }
748 matched = !argu_desc.arg_name.empty() && *pdata == '\0';
749 }
750 return argu_desc;
751}
Jan Eilers7e989832020-06-19 11:47:21 +0100752
Jim Flynn357add22023-04-10 23:26:40 +0100753#else // CXXOPTS_NO_REGEX
Jan Eilers7e989832020-06-19 11:47:21 +0100754
Jim Flynn357add22023-04-10 23:26:40 +0100755namespace {
756CXXOPTS_LINKONCE
757std::basic_regex<char> integer_pattern
758 ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)");
759CXXOPTS_LINKONCE
760std::basic_regex<char> truthy_pattern
761 ("(t|T)(rue)?|1");
762CXXOPTS_LINKONCE
763std::basic_regex<char> falsy_pattern
764 ("(f|F)(alse)?|0");
765CXXOPTS_LINKONCE
766std::basic_regex<char> option_matcher
767 ("--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)");
768CXXOPTS_LINKONCE
769std::basic_regex<char> option_specifier
770 ("([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*");
771CXXOPTS_LINKONCE
772std::basic_regex<char> option_specifier_separator(", *");
773
774} // namespace
775
776inline IntegerDesc SplitInteger(const std::string &text)
777{
778 std::smatch match;
779 std::regex_match(text, match, integer_pattern);
780
781 if (match.length() == 0)
782 {
783 throw_or_mimic<exceptions::incorrect_argument_type>(text);
784 }
785
786 IntegerDesc desc;
787 desc.negative = match[1];
788 desc.base = match[2];
789 desc.value = match[3];
790
791 if (match.length(4) > 0)
792 {
793 desc.base = match[5];
794 desc.value = "0";
795 return desc;
796 }
797
798 return desc;
799}
800
801inline bool IsTrueText(const std::string &text)
802{
803 std::smatch result;
804 std::regex_match(text, result, truthy_pattern);
805 return !result.empty();
806}
807
808inline bool IsFalseText(const std::string &text)
809{
810 std::smatch result;
811 std::regex_match(text, result, falsy_pattern);
812 return !result.empty();
813}
814
815// Gets the option names specified via a single, comma-separated string,
816// and returns the separate, space-discarded, non-empty names
817// (without considering which or how many are single-character)
818inline OptionNames split_option_names(const std::string &text)
819{
820 if (!std::regex_match(text.c_str(), option_specifier))
821 {
822 throw_or_mimic<exceptions::invalid_option_format>(text);
823 }
824
825 OptionNames split_names;
826
827 constexpr int use_non_matches { -1 };
828 auto token_iterator = std::sregex_token_iterator(
829 text.begin(), text.end(), option_specifier_separator, use_non_matches);
830 std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names));
831 return split_names;
832}
833
834inline ArguDesc ParseArgument(const char *arg, bool &matched)
835{
836 std::match_results<const char*> result;
837 std::regex_match(arg, result, option_matcher);
838 matched = !result.empty();
839
840 ArguDesc argu_desc;
841 if (matched) {
842 argu_desc.arg_name = result[1].str();
843 argu_desc.set_value = result[2].length() > 0;
844 argu_desc.value = result[3].str();
845 if (result[4].length() > 0)
846 {
847 argu_desc.grouping = true;
848 argu_desc.arg_name = result[4].str();
849 }
850 }
851
852 return argu_desc;
853}
854
855#endif // CXXOPTS_NO_REGEX
856#undef CXXOPTS_NO_REGEX
857} // namespace parser_tool
858
859namespace detail {
860
861template <typename T, bool B>
862struct SignedCheck;
863
864template <typename T>
865struct SignedCheck<T, true>
866{
867 template <typename U>
868 void
869 operator()(bool negative, U u, const std::string& text)
870 {
871 if (negative)
872 {
873 if (u > static_cast<U>((std::numeric_limits<T>::min)()))
Jan Eilers7e989832020-06-19 11:47:21 +0100874 {
Jim Flynn357add22023-04-10 23:26:40 +0100875 throw_or_mimic<exceptions::incorrect_argument_type>(text);
Jan Eilers7e989832020-06-19 11:47:21 +0100876 }
Jim Flynn357add22023-04-10 23:26:40 +0100877 }
878 else
879 {
880 if (u > static_cast<U>((std::numeric_limits<T>::max)()))
Jan Eilers7e989832020-06-19 11:47:21 +0100881 {
Jim Flynn357add22023-04-10 23:26:40 +0100882 throw_or_mimic<exceptions::incorrect_argument_type>(text);
Jan Eilers7e989832020-06-19 11:47:21 +0100883 }
884 }
Jim Flynn357add22023-04-10 23:26:40 +0100885 }
886};
Jan Eilers7e989832020-06-19 11:47:21 +0100887
Jim Flynn357add22023-04-10 23:26:40 +0100888template <typename T>
889struct SignedCheck<T, false>
890{
891 template <typename U>
892 void
893 operator()(bool, U, const std::string&) const {}
894};
895
896template <typename T, typename U>
897void
898check_signed_range(bool negative, U value, const std::string& text)
899{
900 SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text);
901}
902
903} // namespace detail
904
905template <typename R, typename T>
906void
907checked_negate(R& r, T&& t, const std::string&, std::true_type)
908{
909 // if we got to here, then `t` is a positive number that fits into
910 // `R`. So to avoid MSVC C4146, we first cast it to `R`.
911 // See https://github.com/jarro2783/cxxopts/issues/62 for more details.
912 r = static_cast<R>(-static_cast<R>(t-1)-1);
913}
914
915template <typename R, typename T>
916void
917checked_negate(R&, T&&, const std::string& text, std::false_type)
918{
919 throw_or_mimic<exceptions::incorrect_argument_type>(text);
920}
921
922template <typename T>
923void
924integer_parser(const std::string& text, T& value)
925{
926 parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text);
927
928 using US = typename std::make_unsigned<T>::type;
929 constexpr bool is_signed = std::numeric_limits<T>::is_signed;
930
931 const bool negative = int_desc.negative.length() > 0;
932 const uint8_t base = int_desc.base.length() > 0 ? 16 : 10;
933 const std::string & value_match = int_desc.value;
934
935 US result = 0;
936
937 for (char ch : value_match)
938 {
939 US digit = 0;
940
941 if (ch >= '0' && ch <= '9')
Jan Eilers7e989832020-06-19 11:47:21 +0100942 {
Jim Flynn357add22023-04-10 23:26:40 +0100943 digit = static_cast<US>(ch - '0');
Jan Eilers7e989832020-06-19 11:47:21 +0100944 }
Jim Flynn357add22023-04-10 23:26:40 +0100945 else if (base == 16 && ch >= 'a' && ch <= 'f')
Jan Eilers7e989832020-06-19 11:47:21 +0100946 {
Jim Flynn357add22023-04-10 23:26:40 +0100947 digit = static_cast<US>(ch - 'a' + 10);
Jan Eilers7e989832020-06-19 11:47:21 +0100948 }
Jim Flynn357add22023-04-10 23:26:40 +0100949 else if (base == 16 && ch >= 'A' && ch <= 'F')
Jan Eilers7e989832020-06-19 11:47:21 +0100950 {
Jim Flynn357add22023-04-10 23:26:40 +0100951 digit = static_cast<US>(ch - 'A' + 10);
Jan Eilers7e989832020-06-19 11:47:21 +0100952 }
Jim Flynn357add22023-04-10 23:26:40 +0100953 else
Jan Eilers7e989832020-06-19 11:47:21 +0100954 {
Jim Flynn357add22023-04-10 23:26:40 +0100955 throw_or_mimic<exceptions::incorrect_argument_type>(text);
Jan Eilers7e989832020-06-19 11:47:21 +0100956 }
957
Jim Flynn357add22023-04-10 23:26:40 +0100958 const US next = static_cast<US>(result * base + digit);
959 if (result > next)
Jan Eilers7e989832020-06-19 11:47:21 +0100960 {
Jim Flynn357add22023-04-10 23:26:40 +0100961 throw_or_mimic<exceptions::incorrect_argument_type>(text);
Jan Eilers7e989832020-06-19 11:47:21 +0100962 }
963
Jim Flynn357add22023-04-10 23:26:40 +0100964 result = next;
965 }
Jan Eilers7e989832020-06-19 11:47:21 +0100966
Jim Flynn357add22023-04-10 23:26:40 +0100967 detail::check_signed_range<T>(negative, result, text);
Jan Eilers7e989832020-06-19 11:47:21 +0100968
Jim Flynn357add22023-04-10 23:26:40 +0100969 if (negative)
970 {
971 checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>());
972 }
973 else
974 {
975 value = static_cast<T>(result);
976 }
977}
Jan Eilers7e989832020-06-19 11:47:21 +0100978
Jim Flynn357add22023-04-10 23:26:40 +0100979template <typename T>
980void stringstream_parser(const std::string& text, T& value)
981{
982 std::stringstream in(text);
983 in >> value;
984 if (!in) {
985 throw_or_mimic<exceptions::incorrect_argument_type>(text);
986 }
987}
Jan Eilers7e989832020-06-19 11:47:21 +0100988
Jim Flynn357add22023-04-10 23:26:40 +0100989template <typename T,
990 typename std::enable_if<std::is_integral<T>::value>::type* = nullptr
991 >
992void parse_value(const std::string& text, T& value)
993{
994 integer_parser(text, value);
995}
Jan Eilers7e989832020-06-19 11:47:21 +0100996
Jim Flynn357add22023-04-10 23:26:40 +0100997inline
998void
999parse_value(const std::string& text, bool& value)
1000{
1001 if (parser_tool::IsTrueText(text))
1002 {
1003 value = true;
1004 return;
1005 }
Jan Eilers7e989832020-06-19 11:47:21 +01001006
Jim Flynn357add22023-04-10 23:26:40 +01001007 if (parser_tool::IsFalseText(text))
1008 {
1009 value = false;
1010 return;
1011 }
Jan Eilers7e989832020-06-19 11:47:21 +01001012
Jim Flynn357add22023-04-10 23:26:40 +01001013 throw_or_mimic<exceptions::incorrect_argument_type>(text);
1014}
Jan Eilers7e989832020-06-19 11:47:21 +01001015
Jim Flynn357add22023-04-10 23:26:40 +01001016inline
1017void
1018parse_value(const std::string& text, std::string& value)
1019{
1020 value = text;
1021}
Jan Eilers7e989832020-06-19 11:47:21 +01001022
Jim Flynn357add22023-04-10 23:26:40 +01001023// The fallback parser. It uses the stringstream parser to parse all types
1024// that have not been overloaded explicitly. It has to be placed in the
1025// source code before all other more specialized templates.
1026template <typename T,
1027 typename std::enable_if<!std::is_integral<T>::value>::type* = nullptr
1028 >
1029void
1030parse_value(const std::string& text, T& value) {
1031 stringstream_parser(text, value);
1032}
Jan Eilers7e989832020-06-19 11:47:21 +01001033
Jim Flynn357add22023-04-10 23:26:40 +01001034template <typename T>
1035void
1036parse_value(const std::string& text, std::vector<T>& value)
1037{
1038 if (text.empty()) {
1039 T v;
1040 parse_value(text, v);
1041 value.emplace_back(std::move(v));
1042 return;
1043 }
1044 std::stringstream in(text);
1045 std::string token;
1046 while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
1047 T v;
1048 parse_value(token, v);
1049 value.emplace_back(std::move(v));
1050 }
1051}
Jan Eilers7e989832020-06-19 11:47:21 +01001052
1053#ifdef CXXOPTS_HAS_OPTIONAL
Jim Flynn357add22023-04-10 23:26:40 +01001054template <typename T>
1055void
1056parse_value(const std::string& text, std::optional<T>& value)
1057{
1058 T result;
1059 parse_value(text, result);
1060 value = std::move(result);
1061}
Jan Eilers7e989832020-06-19 11:47:21 +01001062#endif
1063
Jim Flynn357add22023-04-10 23:26:40 +01001064inline
1065void parse_value(const std::string& text, char& c)
1066{
1067 if (text.length() != 1)
1068 {
1069 throw_or_mimic<exceptions::incorrect_argument_type>(text);
1070 }
Jan Eilers7e989832020-06-19 11:47:21 +01001071
Jim Flynn357add22023-04-10 23:26:40 +01001072 c = text[0];
1073}
1074
1075template <typename T>
1076struct type_is_container
1077{
1078 static constexpr bool value = false;
1079};
1080
1081template <typename T>
1082struct type_is_container<std::vector<T>>
1083{
1084 static constexpr bool value = true;
1085};
1086
1087template <typename T>
1088class abstract_value : public Value
1089{
1090 using Self = abstract_value<T>;
1091
1092 public:
1093 abstract_value()
1094 : m_result(std::make_shared<T>())
1095 , m_store(m_result.get())
1096 {
1097 }
1098
1099 explicit abstract_value(T* t)
1100 : m_store(t)
1101 {
1102 }
1103
1104 ~abstract_value() override = default;
1105
1106 abstract_value& operator=(const abstract_value&) = default;
1107
1108 abstract_value(const abstract_value& rhs)
1109 {
1110 if (rhs.m_result)
1111 {
1112 m_result = std::make_shared<T>();
1113 m_store = m_result.get();
1114 }
1115 else
1116 {
1117 m_store = rhs.m_store;
Jan Eilers7e989832020-06-19 11:47:21 +01001118 }
1119
Jim Flynn357add22023-04-10 23:26:40 +01001120 m_default = rhs.m_default;
1121 m_implicit = rhs.m_implicit;
1122 m_default_value = rhs.m_default_value;
1123 m_implicit_value = rhs.m_implicit_value;
1124 }
Jan Eilers7e989832020-06-19 11:47:21 +01001125
Jim Flynn357add22023-04-10 23:26:40 +01001126 void
1127 parse(const std::string& text) const override
Jan Eilers7e989832020-06-19 11:47:21 +01001128 {
Jim Flynn357add22023-04-10 23:26:40 +01001129 parse_value(text, *m_store);
1130 }
1131
1132 bool
1133 is_container() const override
1134 {
1135 return type_is_container<T>::value;
1136 }
1137
1138 void
1139 parse() const override
1140 {
1141 parse_value(m_default_value, *m_store);
1142 }
1143
1144 bool
1145 has_default() const override
1146 {
1147 return m_default;
1148 }
1149
1150 bool
1151 has_implicit() const override
1152 {
1153 return m_implicit;
1154 }
1155
1156 std::shared_ptr<Value>
1157 default_value(const std::string& value) override
1158 {
1159 m_default = true;
1160 m_default_value = value;
1161 return shared_from_this();
1162 }
1163
1164 std::shared_ptr<Value>
1165 implicit_value(const std::string& value) override
1166 {
1167 m_implicit = true;
1168 m_implicit_value = value;
1169 return shared_from_this();
1170 }
1171
1172 std::shared_ptr<Value>
1173 no_implicit_value() override
1174 {
1175 m_implicit = false;
1176 return shared_from_this();
1177 }
1178
1179 std::string
1180 get_default_value() const override
1181 {
1182 return m_default_value;
1183 }
1184
1185 std::string
1186 get_implicit_value() const override
1187 {
1188 return m_implicit_value;
1189 }
1190
1191 bool
1192 is_boolean() const override
1193 {
1194 return std::is_same<T, bool>::value;
1195 }
1196
1197 const T&
1198 get() const
1199 {
1200 if (m_store == nullptr)
1201 {
1202 return *m_result;
1203 }
1204 return *m_store;
1205 }
1206
1207 protected:
1208 std::shared_ptr<T> m_result{};
1209 T* m_store{};
1210
1211 bool m_default = false;
1212 bool m_implicit = false;
1213
1214 std::string m_default_value{};
1215 std::string m_implicit_value{};
1216};
1217
1218template <typename T>
1219class standard_value : public abstract_value<T>
1220{
1221 public:
1222 using abstract_value<T>::abstract_value;
1223
1224 CXXOPTS_NODISCARD
1225 std::shared_ptr<Value>
1226 clone() const override
1227 {
1228 return std::make_shared<standard_value<T>>(*this);
1229 }
1230};
1231
1232template <>
1233class standard_value<bool> : public abstract_value<bool>
1234{
1235 public:
1236 ~standard_value() override = default;
1237
1238 standard_value()
1239 {
1240 set_default_and_implicit();
1241 }
1242
1243 explicit standard_value(bool* b)
1244 : abstract_value(b)
1245 {
1246 m_implicit = true;
1247 m_implicit_value = "true";
1248 }
1249
1250 std::shared_ptr<Value>
1251 clone() const override
1252 {
1253 return std::make_shared<standard_value<bool>>(*this);
1254 }
1255
1256 private:
1257
1258 void
1259 set_default_and_implicit()
1260 {
1261 m_default = true;
1262 m_default_value = "false";
1263 m_implicit = true;
1264 m_implicit_value = "true";
1265 }
1266};
1267
1268} // namespace values
1269
1270template <typename T>
1271std::shared_ptr<Value>
1272value()
1273{
1274 return std::make_shared<values::standard_value<T>>();
1275}
1276
1277template <typename T>
1278std::shared_ptr<Value>
1279value(T& t)
1280{
1281 return std::make_shared<values::standard_value<T>>(&t);
1282}
1283
1284class OptionAdder;
1285
1286CXXOPTS_NODISCARD
1287inline
1288const std::string&
1289first_or_empty(const OptionNames& long_names)
1290{
1291 static const std::string empty{""};
1292 return long_names.empty() ? empty : long_names.front();
1293}
1294
1295class OptionDetails
1296{
1297 public:
1298 OptionDetails
1299 (
1300 std::string short_,
1301 OptionNames long_,
1302 String desc,
1303 std::shared_ptr<const Value> val
1304 )
1305 : m_short(std::move(short_))
1306 , m_long(std::move(long_))
1307 , m_desc(std::move(desc))
1308 , m_value(std::move(val))
1309 , m_count(0)
1310 {
1311 m_hash = std::hash<std::string>{}(first_long_name() + m_short);
1312 }
1313
1314 OptionDetails(const OptionDetails& rhs)
1315 : m_desc(rhs.m_desc)
1316 , m_value(rhs.m_value->clone())
1317 , m_count(rhs.m_count)
1318 {
1319 }
1320
1321 OptionDetails(OptionDetails&& rhs) = default;
1322
1323 CXXOPTS_NODISCARD
1324 const String&
1325 description() const
1326 {
1327 return m_desc;
1328 }
1329
1330 CXXOPTS_NODISCARD
1331 const Value&
1332 value() const {
1333 return *m_value;
1334 }
1335
1336 CXXOPTS_NODISCARD
1337 std::shared_ptr<Value>
1338 make_storage() const
1339 {
1340 return m_value->clone();
1341 }
1342
1343 CXXOPTS_NODISCARD
1344 const std::string&
1345 short_name() const
1346 {
1347 return m_short;
1348 }
1349
1350 CXXOPTS_NODISCARD
1351 const std::string&
1352 first_long_name() const
1353 {
1354 return first_or_empty(m_long);
1355 }
1356
1357 CXXOPTS_NODISCARD
1358 const std::string&
1359 essential_name() const
1360 {
1361 return m_long.empty() ? m_short : m_long.front();
1362 }
1363
1364 CXXOPTS_NODISCARD
1365 const OptionNames &
1366 long_names() const
1367 {
1368 return m_long;
1369 }
1370
1371 std::size_t
1372 hash() const
1373 {
1374 return m_hash;
1375 }
1376
1377 private:
1378 std::string m_short{};
1379 OptionNames m_long{};
1380 String m_desc{};
1381 std::shared_ptr<const Value> m_value{};
1382 int m_count;
1383
1384 std::size_t m_hash{};
1385};
1386
1387struct HelpOptionDetails
1388{
1389 std::string s;
1390 OptionNames l;
1391 String desc;
1392 bool has_default;
1393 std::string default_value;
1394 bool has_implicit;
1395 std::string implicit_value;
1396 std::string arg_help;
1397 bool is_container;
1398 bool is_boolean;
1399};
1400
1401struct HelpGroupDetails
1402{
1403 std::string name{};
1404 std::string description{};
1405 std::vector<HelpOptionDetails> options{};
1406};
1407
1408class OptionValue
1409{
1410 public:
1411 void
1412 parse
1413 (
1414 const std::shared_ptr<const OptionDetails>& details,
1415 const std::string& text
1416 )
1417 {
1418 ensure_value(details);
1419 ++m_count;
1420 m_value->parse(text);
1421 m_long_names = &details->long_names();
1422 }
1423
1424 void
1425 parse_default(const std::shared_ptr<const OptionDetails>& details)
1426 {
1427 ensure_value(details);
1428 m_default = true;
1429 m_long_names = &details->long_names();
1430 m_value->parse();
1431 }
1432
1433 void
1434 parse_no_value(const std::shared_ptr<const OptionDetails>& details)
1435 {
1436 m_long_names = &details->long_names();
1437 }
1438
1439#if defined(CXXOPTS_NULL_DEREF_IGNORE)
1440CXXOPTS_DIAGNOSTIC_PUSH
1441CXXOPTS_IGNORE_WARNING("-Wnull-dereference")
1442#endif
1443
1444 CXXOPTS_NODISCARD
1445 std::size_t
1446 count() const noexcept
1447 {
1448 return m_count;
1449 }
1450
1451#if defined(CXXOPTS_NULL_DEREF_IGNORE)
1452CXXOPTS_DIAGNOSTIC_POP
1453#endif
1454
1455 // TODO: maybe default options should count towards the number of arguments
1456 CXXOPTS_NODISCARD
1457 bool
1458 has_default() const noexcept
1459 {
1460 return m_default;
Jan Eilers7e989832020-06-19 11:47:21 +01001461 }
1462
1463 template <typename T>
Jim Flynn357add22023-04-10 23:26:40 +01001464 const T&
1465 as() const
Jan Eilers7e989832020-06-19 11:47:21 +01001466 {
Jim Flynn357add22023-04-10 23:26:40 +01001467 if (m_value == nullptr) {
1468 throw_or_mimic<exceptions::option_has_no_value>(
1469 m_long_names == nullptr ? "" : first_or_empty(*m_long_names));
1470 }
1471
1472 return CXXOPTS_RTTI_CAST<const values::standard_value<T>&>(*m_value).get();
Jan Eilers7e989832020-06-19 11:47:21 +01001473 }
1474
Jim Flynn357add22023-04-10 23:26:40 +01001475 private:
1476 void
1477 ensure_value(const std::shared_ptr<const OptionDetails>& details)
1478 {
1479 if (m_value == nullptr)
1480 {
1481 m_value = details->make_storage();
1482 }
1483 }
Jan Eilers7e989832020-06-19 11:47:21 +01001484
Jim Flynn357add22023-04-10 23:26:40 +01001485
1486 const OptionNames * m_long_names = nullptr;
1487 // Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
1488 // where the key has the string we point to.
1489 std::shared_ptr<Value> m_value{};
1490 std::size_t m_count = 0;
1491 bool m_default = false;
1492};
1493
1494class KeyValue
1495{
1496 public:
1497 KeyValue(std::string key_, std::string value_) noexcept
1498 : m_key(std::move(key_))
1499 , m_value(std::move(value_))
1500 {
1501 }
1502
1503 CXXOPTS_NODISCARD
1504 const std::string&
1505 key() const
1506 {
1507 return m_key;
1508 }
1509
1510 CXXOPTS_NODISCARD
1511 const std::string&
1512 value() const
1513 {
1514 return m_value;
1515 }
1516
1517 template <typename T>
1518 T
1519 as() const
1520 {
1521 T result;
1522 values::parse_value(m_value, result);
1523 return result;
1524 }
1525
1526 private:
1527 std::string m_key;
1528 std::string m_value;
1529};
1530
1531using ParsedHashMap = std::unordered_map<std::size_t, OptionValue>;
1532using NameHashMap = std::unordered_map<std::string, std::size_t>;
1533
1534class ParseResult
1535{
1536 public:
1537 class Iterator
Jan Eilers7e989832020-06-19 11:47:21 +01001538 {
1539 public:
Jim Flynn357add22023-04-10 23:26:40 +01001540 using iterator_category = std::forward_iterator_tag;
1541 using value_type = KeyValue;
1542 using difference_type = void;
1543 using pointer = const KeyValue*;
1544 using reference = const KeyValue&;
1545
1546 Iterator() = default;
1547 Iterator(const Iterator&) = default;
1548
1549// GCC complains about m_iter not being initialised in the member
1550// initializer list
1551CXXOPTS_DIAGNOSTIC_PUSH
1552CXXOPTS_IGNORE_WARNING("-Weffc++")
1553 Iterator(const ParseResult *pr, bool end=false)
1554 : m_pr(pr)
Jan Eilers7e989832020-06-19 11:47:21 +01001555 {
Jim Flynn357add22023-04-10 23:26:40 +01001556 if (end)
Jan Eilers7e989832020-06-19 11:47:21 +01001557 {
Jim Flynn357add22023-04-10 23:26:40 +01001558 m_sequential = false;
1559 m_iter = m_pr->m_defaults.end();
Jan Eilers7e989832020-06-19 11:47:21 +01001560 }
1561 else
1562 {
Jim Flynn357add22023-04-10 23:26:40 +01001563 m_sequential = true;
1564 m_iter = m_pr->m_sequential.begin();
Jan Eilers7e989832020-06-19 11:47:21 +01001565
Jim Flynn357add22023-04-10 23:26:40 +01001566 if (m_iter == m_pr->m_sequential.end())
Jan Eilers7e989832020-06-19 11:47:21 +01001567 {
Jim Flynn357add22023-04-10 23:26:40 +01001568 m_sequential = false;
1569 m_iter = m_pr->m_defaults.begin();
Jan Eilers7e989832020-06-19 11:47:21 +01001570 }
1571 }
Jan Eilers7e989832020-06-19 11:47:21 +01001572 }
Jim Flynn357add22023-04-10 23:26:40 +01001573CXXOPTS_DIAGNOSTIC_POP
Jan Eilers7e989832020-06-19 11:47:21 +01001574
Jim Flynn357add22023-04-10 23:26:40 +01001575 Iterator& operator++()
Jan Eilers7e989832020-06-19 11:47:21 +01001576 {
Jim Flynn357add22023-04-10 23:26:40 +01001577 ++m_iter;
1578 if(m_sequential && m_iter == m_pr->m_sequential.end())
Jan Eilers7e989832020-06-19 11:47:21 +01001579 {
Jim Flynn357add22023-04-10 23:26:40 +01001580 m_sequential = false;
1581 m_iter = m_pr->m_defaults.begin();
1582 return *this;
Jan Eilers7e989832020-06-19 11:47:21 +01001583 }
Jim Flynn357add22023-04-10 23:26:40 +01001584 return *this;
Jan Eilers7e989832020-06-19 11:47:21 +01001585 }
Jim Flynn357add22023-04-10 23:26:40 +01001586
1587 Iterator operator++(int)
1588 {
1589 Iterator retval = *this;
1590 ++(*this);
1591 return retval;
1592 }
1593
1594 bool operator==(const Iterator& other) const
1595 {
1596 return (m_sequential == other.m_sequential) && (m_iter == other.m_iter);
1597 }
1598
1599 bool operator!=(const Iterator& other) const
1600 {
1601 return !(*this == other);
1602 }
1603
1604 const KeyValue& operator*()
1605 {
1606 return *m_iter;
1607 }
1608
1609 const KeyValue* operator->()
1610 {
1611 return m_iter.operator->();
1612 }
1613
1614 private:
1615 const ParseResult* m_pr;
1616 std::vector<KeyValue>::const_iterator m_iter;
1617 bool m_sequential = true;
1618 };
1619
1620 ParseResult() = default;
1621 ParseResult(const ParseResult&) = default;
1622
1623 ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential,
1624 std::vector<KeyValue> default_opts, std::vector<std::string>&& unmatched_args)
1625 : m_keys(std::move(keys))
1626 , m_values(std::move(values))
1627 , m_sequential(std::move(sequential))
1628 , m_defaults(std::move(default_opts))
1629 , m_unmatched(std::move(unmatched_args))
1630 {
1631 }
1632
1633 ParseResult& operator=(ParseResult&&) = default;
1634 ParseResult& operator=(const ParseResult&) = default;
1635
1636 Iterator
1637 begin() const
1638 {
1639 return Iterator(this);
1640 }
1641
1642 Iterator
1643 end() const
1644 {
1645 return Iterator(this, true);
1646 }
1647
1648 std::size_t
1649 count(const std::string& o) const
1650 {
1651 auto iter = m_keys.find(o);
1652 if (iter == m_keys.end())
1653 {
1654 return 0;
1655 }
1656
1657 auto viter = m_values.find(iter->second);
1658
1659 if (viter == m_values.end())
1660 {
1661 return 0;
1662 }
1663
1664 return viter->second.count();
1665 }
1666
1667 const OptionValue&
1668 operator[](const std::string& option) const
1669 {
1670 auto iter = m_keys.find(option);
1671
1672 if (iter == m_keys.end())
1673 {
1674 throw_or_mimic<exceptions::requested_option_not_present>(option);
1675 }
1676
1677 auto viter = m_values.find(iter->second);
1678
1679 if (viter == m_values.end())
1680 {
1681 throw_or_mimic<exceptions::requested_option_not_present>(option);
1682 }
1683
1684 return viter->second;
1685 }
1686
1687 const std::vector<KeyValue>&
1688 arguments() const
1689 {
1690 return m_sequential;
1691 }
1692
1693 const std::vector<std::string>&
1694 unmatched() const
1695 {
1696 return m_unmatched;
1697 }
1698
1699 const std::vector<KeyValue>&
1700 defaults() const
1701 {
1702 return m_defaults;
1703 }
1704
1705 const std::string
1706 arguments_string() const
1707 {
1708 std::string result;
1709 for(const auto& kv: m_sequential)
1710 {
1711 result += kv.key() + " = " + kv.value() + "\n";
1712 }
1713 for(const auto& kv: m_defaults)
1714 {
1715 result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n";
1716 }
1717 return result;
1718 }
1719
1720 private:
1721 NameHashMap m_keys{};
1722 ParsedHashMap m_values{};
1723 std::vector<KeyValue> m_sequential{};
1724 std::vector<KeyValue> m_defaults{};
1725 std::vector<std::string> m_unmatched{};
1726};
1727
1728struct Option
1729{
1730 Option
1731 (
1732 std::string opts,
1733 std::string desc,
1734 std::shared_ptr<const Value> value = ::cxxopts::value<bool>(),
1735 std::string arg_help = ""
1736 )
1737 : opts_(std::move(opts))
1738 , desc_(std::move(desc))
1739 , value_(std::move(value))
1740 , arg_help_(std::move(arg_help))
1741 {
1742 }
1743
1744 std::string opts_;
1745 std::string desc_;
1746 std::shared_ptr<const Value> value_;
1747 std::string arg_help_;
1748};
1749
1750using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
1751using PositionalList = std::vector<std::string>;
1752using PositionalListIterator = PositionalList::const_iterator;
1753
1754class OptionParser
1755{
1756 public:
1757 OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)
1758 : m_options(options)
1759 , m_positional(positional)
1760 , m_allow_unrecognised(allow_unrecognised)
1761 {
1762 }
1763
1764 ParseResult
1765 parse(int argc, const char* const* argv);
1766
1767 bool
1768 consume_positional(const std::string& a, PositionalListIterator& next);
1769
1770 void
1771 checked_parse_arg
1772 (
1773 int argc,
1774 const char* const* argv,
1775 int& current,
1776 const std::shared_ptr<OptionDetails>& value,
1777 const std::string& name
1778 );
1779
1780 void
1781 add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg);
1782
1783 void
1784 parse_option
1785 (
1786 const std::shared_ptr<OptionDetails>& value,
1787 const std::string& name,
1788 const std::string& arg = ""
1789 );
1790
1791 void
1792 parse_default(const std::shared_ptr<OptionDetails>& details);
1793
1794 void
1795 parse_no_value(const std::shared_ptr<OptionDetails>& details);
1796
1797 private:
1798
1799 void finalise_aliases();
1800
1801 const OptionMap& m_options;
1802 const PositionalList& m_positional;
1803
1804 std::vector<KeyValue> m_sequential{};
1805 std::vector<KeyValue> m_defaults{};
1806 bool m_allow_unrecognised;
1807
1808 ParsedHashMap m_parsed{};
1809 NameHashMap m_keys{};
1810};
1811
1812class Options
1813{
1814 public:
1815
1816 explicit Options(std::string program_name, std::string help_string = "")
1817 : m_program(std::move(program_name))
1818 , m_help_string(toLocalString(std::move(help_string)))
1819 , m_custom_help("[OPTION...]")
1820 , m_positional_help("positional parameters")
1821 , m_show_positional(false)
1822 , m_allow_unrecognised(false)
1823 , m_width(76)
1824 , m_tab_expansion(false)
1825 , m_options(std::make_shared<OptionMap>())
1826 {
1827 }
1828
1829 Options&
1830 positional_help(std::string help_text)
1831 {
1832 m_positional_help = std::move(help_text);
1833 return *this;
1834 }
1835
1836 Options&
1837 custom_help(std::string help_text)
1838 {
1839 m_custom_help = std::move(help_text);
1840 return *this;
1841 }
1842
1843 Options&
1844 show_positional_help()
1845 {
1846 m_show_positional = true;
1847 return *this;
1848 }
1849
1850 Options&
1851 allow_unrecognised_options()
1852 {
1853 m_allow_unrecognised = true;
1854 return *this;
1855 }
1856
1857 Options&
1858 set_width(std::size_t width)
1859 {
1860 m_width = width;
1861 return *this;
1862 }
1863
1864 Options&
1865 set_tab_expansion(bool expansion=true)
1866 {
1867 m_tab_expansion = expansion;
1868 return *this;
1869 }
1870
1871 ParseResult
1872 parse(int argc, const char* const* argv);
1873
1874 OptionAdder
1875 add_options(std::string group = "");
1876
1877 void
1878 add_options
1879 (
1880 const std::string& group,
1881 std::initializer_list<Option> options
1882 );
1883
1884 void
1885 add_option
1886 (
1887 const std::string& group,
1888 const Option& option
1889 );
1890
1891 void
1892 add_option
1893 (
1894 const std::string& group,
1895 const std::string& s,
1896 const OptionNames& l,
1897 std::string desc,
1898 const std::shared_ptr<const Value>& value,
1899 std::string arg_help
1900 );
1901
1902 void
1903 add_option
1904 (
1905 const std::string& group,
1906 const std::string& short_name,
1907 const std::string& single_long_name,
1908 std::string desc,
1909 const std::shared_ptr<const Value>& value,
1910 std::string arg_help
1911 )
1912 {
1913 OptionNames long_names;
1914 long_names.emplace_back(single_long_name);
1915 add_option(group, short_name, long_names, desc, value, arg_help);
1916 }
1917
1918 //parse positional arguments into the given option
1919 void
1920 parse_positional(std::string option);
1921
1922 void
1923 parse_positional(std::vector<std::string> options);
1924
1925 void
1926 parse_positional(std::initializer_list<std::string> options);
1927
1928 template <typename Iterator>
1929 void
1930 parse_positional(Iterator begin, Iterator end) {
1931 parse_positional(std::vector<std::string>{begin, end});
1932 }
1933
1934 std::string
1935 help(const std::vector<std::string>& groups = {}, bool print_usage=true) const;
1936
1937 std::vector<std::string>
1938 groups() const;
1939
1940 const HelpGroupDetails&
1941 group_help(const std::string& group) const;
1942
1943 const std::string& program() const
1944 {
1945 return m_program;
1946 }
1947
1948 private:
1949
1950 void
1951 add_one_option
1952 (
1953 const std::string& option,
1954 const std::shared_ptr<OptionDetails>& details
1955 );
1956
1957 String
1958 help_one_group(const std::string& group) const;
1959
1960 void
1961 generate_group_help
1962 (
1963 String& result,
1964 const std::vector<std::string>& groups
1965 ) const;
1966
1967 void
1968 generate_all_groups_help(String& result) const;
1969
1970 std::string m_program{};
1971 String m_help_string{};
1972 std::string m_custom_help{};
1973 std::string m_positional_help{};
1974 bool m_show_positional;
1975 bool m_allow_unrecognised;
1976 std::size_t m_width;
1977 bool m_tab_expansion;
1978
1979 std::shared_ptr<OptionMap> m_options;
1980 std::vector<std::string> m_positional{};
1981 std::unordered_set<std::string> m_positional_set{};
1982
1983 //mapping from groups to help options
1984 std::map<std::string, HelpGroupDetails> m_help{};
1985};
1986
1987class OptionAdder
1988{
1989 public:
1990
1991 OptionAdder(Options& options, std::string group)
1992 : m_options(options), m_group(std::move(group))
1993 {
1994 }
1995
1996 OptionAdder&
1997 operator()
1998 (
1999 const std::string& opts,
2000 const std::string& desc,
2001 const std::shared_ptr<const Value>& value
2002 = ::cxxopts::value<bool>(),
2003 std::string arg_help = ""
2004 );
2005
2006 private:
2007 Options& m_options;
2008 std::string m_group;
2009};
2010
2011namespace {
2012constexpr std::size_t OPTION_LONGEST = 30;
2013constexpr std::size_t OPTION_DESC_GAP = 2;
2014
2015String
2016format_option
2017(
2018 const HelpOptionDetails& o
2019)
2020{
2021 const auto& s = o.s;
2022 const auto& l = first_or_empty(o.l);
2023
2024 String result = " ";
2025
2026 if (!s.empty())
2027 {
2028 result += "-" + toLocalString(s);
2029 if (!l.empty())
2030 {
2031 result += ",";
2032 }
2033 }
2034 else
2035 {
2036 result += " ";
2037 }
2038
2039 if (!l.empty())
2040 {
2041 result += " --" + toLocalString(l);
2042 }
2043
2044 auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg";
2045
2046 if (!o.is_boolean)
2047 {
2048 if (o.has_implicit)
2049 {
2050 result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
2051 }
2052 else
2053 {
2054 result += " " + arg;
2055 }
2056 }
2057
2058 return result;
2059}
2060
2061String
2062format_description
2063(
2064 const HelpOptionDetails& o,
2065 std::size_t start,
2066 std::size_t allowed,
2067 bool tab_expansion
2068)
2069{
2070 auto desc = o.desc;
2071
2072 if (o.has_default && (!o.is_boolean || o.default_value != "false"))
2073 {
2074 if(!o.default_value.empty())
2075 {
2076 desc += toLocalString(" (default: " + o.default_value + ")");
2077 }
2078 else
2079 {
2080 desc += toLocalString(" (default: \"\")");
2081 }
2082 }
2083
2084 String result;
2085
2086 if (tab_expansion)
2087 {
2088 String desc2;
2089 auto size = std::size_t{ 0 };
2090 for (auto c = std::begin(desc); c != std::end(desc); ++c)
2091 {
2092 if (*c == '\n')
2093 {
2094 desc2 += *c;
2095 size = 0;
2096 }
2097 else if (*c == '\t')
2098 {
2099 auto skip = 8 - size % 8;
2100 stringAppend(desc2, skip, ' ');
2101 size += skip;
2102 }
2103 else
2104 {
2105 desc2 += *c;
2106 ++size;
2107 }
2108 }
2109 desc = desc2;
2110 }
2111
2112 desc += " ";
2113
2114 auto current = std::begin(desc);
2115 auto previous = current;
2116 auto startLine = current;
2117 auto lastSpace = current;
2118
2119 auto size = std::size_t{};
2120
2121 bool appendNewLine;
2122 bool onlyWhiteSpace = true;
2123
2124 while (current != std::end(desc))
2125 {
2126 appendNewLine = false;
2127 if (*previous == ' ' || *previous == '\t')
2128 {
2129 lastSpace = current;
2130 }
2131 if (*current != ' ' && *current != '\t')
2132 {
2133 onlyWhiteSpace = false;
2134 }
2135
2136 while (*current == '\n')
2137 {
2138 previous = current;
2139 ++current;
2140 appendNewLine = true;
2141 }
2142
2143 if (!appendNewLine && size >= allowed)
2144 {
2145 if (lastSpace != startLine)
2146 {
2147 current = lastSpace;
2148 previous = current;
2149 }
2150 appendNewLine = true;
2151 }
2152
2153 if (appendNewLine)
2154 {
2155 stringAppend(result, startLine, current);
2156 startLine = current;
2157 lastSpace = current;
2158
2159 if (*previous != '\n')
2160 {
2161 stringAppend(result, "\n");
2162 }
2163
2164 stringAppend(result, start, ' ');
2165
2166 if (*previous != '\n')
2167 {
2168 stringAppend(result, lastSpace, current);
2169 }
2170
2171 onlyWhiteSpace = true;
2172 size = 0;
2173 }
2174
2175 previous = current;
2176 ++current;
2177 ++size;
2178 }
2179
2180 //append whatever is left but ignore whitespace
2181 if (!onlyWhiteSpace)
2182 {
2183 stringAppend(result, startLine, previous);
2184 }
2185
2186 return result;
2187}
2188
2189} // namespace
Jan Eilers7e989832020-06-19 11:47:21 +01002190
2191inline
Jan Eilers7e989832020-06-19 11:47:21 +01002192void
2193Options::add_options
2194(
2195 const std::string &group,
2196 std::initializer_list<Option> options
2197)
2198{
2199 OptionAdder option_adder(*this, group);
2200 for (const auto &option: options)
2201 {
2202 option_adder(option.opts_, option.desc_, option.value_, option.arg_help_);
2203 }
2204}
2205
2206inline
2207OptionAdder
2208Options::add_options(std::string group)
2209{
2210 return OptionAdder(*this, std::move(group));
2211}
2212
2213inline
2214OptionAdder&
2215OptionAdder::operator()
2216(
2217 const std::string& opts,
2218 const std::string& desc,
2219 const std::shared_ptr<const Value>& value,
2220 std::string arg_help
2221)
2222{
Jim Flynn357add22023-04-10 23:26:40 +01002223 OptionNames option_names = values::parser_tool::split_option_names(opts);
2224 // Note: All names will be non-empty; but we must separate the short
2225 // (length-1) and longer names
2226 std::string short_name {""};
2227 auto first_short_name_iter =
2228 std::partition(option_names.begin(), option_names.end(),
2229 [&](const std::string& name) { return name.length() > 1; }
2230 );
2231 auto num_length_1_names = (option_names.end() - first_short_name_iter);
2232 switch(num_length_1_names) {
2233 case 1:
2234 short_name = *first_short_name_iter;
2235 option_names.erase(first_short_name_iter);
2236 break;
2237 case 0:
2238 break;
2239 default:
2240 throw_or_mimic<exceptions::invalid_option_format>(opts);
2241 };
Jan Eilers7e989832020-06-19 11:47:21 +01002242
2243 m_options.add_option
2244 (
2245 m_group,
Jim Flynn357add22023-04-10 23:26:40 +01002246 short_name,
2247 option_names,
Jan Eilers7e989832020-06-19 11:47:21 +01002248 desc,
2249 value,
2250 std::move(arg_help)
2251 );
2252
2253 return *this;
2254}
2255
2256inline
2257void
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002258OptionParser::parse_default(const std::shared_ptr<OptionDetails>& details)
Jan Eilers7e989832020-06-19 11:47:21 +01002259{
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002260 // TODO: remove the duplicate code here
2261 auto& store = m_parsed[details->hash()];
2262 store.parse_default(details);
Jim Flynn357add22023-04-10 23:26:40 +01002263 m_defaults.emplace_back(details->essential_name(), details->value().get_default_value());
2264}
2265
2266inline
2267void
2268OptionParser::parse_no_value(const std::shared_ptr<OptionDetails>& details)
2269{
2270 auto& store = m_parsed[details->hash()];
2271 store.parse_no_value(details);
Jan Eilers7e989832020-06-19 11:47:21 +01002272}
2273
2274inline
2275void
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002276OptionParser::parse_option
Jan Eilers7e989832020-06-19 11:47:21 +01002277(
2278 const std::shared_ptr<OptionDetails>& value,
2279 const std::string& /*name*/,
2280 const std::string& arg
2281)
2282{
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002283 auto hash = value->hash();
2284 auto& result = m_parsed[hash];
Jan Eilers7e989832020-06-19 11:47:21 +01002285 result.parse(value, arg);
2286
Jim Flynn357add22023-04-10 23:26:40 +01002287 m_sequential.emplace_back(value->essential_name(), arg);
Jan Eilers7e989832020-06-19 11:47:21 +01002288}
2289
2290inline
2291void
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002292OptionParser::checked_parse_arg
Jan Eilers7e989832020-06-19 11:47:21 +01002293(
2294 int argc,
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002295 const char* const* argv,
Jan Eilers7e989832020-06-19 11:47:21 +01002296 int& current,
2297 const std::shared_ptr<OptionDetails>& value,
2298 const std::string& name
2299)
2300{
2301 if (current + 1 >= argc)
2302 {
2303 if (value->value().has_implicit())
2304 {
2305 parse_option(value, name, value->value().get_implicit_value());
2306 }
2307 else
2308 {
Jim Flynn357add22023-04-10 23:26:40 +01002309 throw_or_mimic<exceptions::missing_argument>(name);
Jan Eilers7e989832020-06-19 11:47:21 +01002310 }
2311 }
2312 else
2313 {
2314 if (value->value().has_implicit())
2315 {
2316 parse_option(value, name, value->value().get_implicit_value());
2317 }
2318 else
2319 {
2320 parse_option(value, name, argv[current + 1]);
2321 ++current;
2322 }
2323 }
2324}
2325
2326inline
2327void
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002328OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg)
Jan Eilers7e989832020-06-19 11:47:21 +01002329{
Jan Eilers7e989832020-06-19 11:47:21 +01002330 parse_option(iter->second, option, arg);
2331}
2332
2333inline
2334bool
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002335OptionParser::consume_positional(const std::string& a, PositionalListIterator& next)
Jan Eilers7e989832020-06-19 11:47:21 +01002336{
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002337 while (next != m_positional.end())
Jan Eilers7e989832020-06-19 11:47:21 +01002338 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002339 auto iter = m_options.find(*next);
2340 if (iter != m_options.end())
Jan Eilers7e989832020-06-19 11:47:21 +01002341 {
Jan Eilers7e989832020-06-19 11:47:21 +01002342 if (!iter->second->value().is_container())
2343 {
Jim Flynn357add22023-04-10 23:26:40 +01002344 auto& result = m_parsed[iter->second->hash()];
Jan Eilers7e989832020-06-19 11:47:21 +01002345 if (result.count() == 0)
2346 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002347 add_to_option(iter, *next, a);
2348 ++next;
Jan Eilers7e989832020-06-19 11:47:21 +01002349 return true;
2350 }
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002351 ++next;
Jan Eilers7e989832020-06-19 11:47:21 +01002352 continue;
2353 }
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002354 add_to_option(iter, *next, a);
Jan Eilers7e989832020-06-19 11:47:21 +01002355 return true;
2356 }
Jim Flynn357add22023-04-10 23:26:40 +01002357 throw_or_mimic<exceptions::no_such_option>(*next);
Jan Eilers7e989832020-06-19 11:47:21 +01002358 }
2359
2360 return false;
2361}
2362
2363inline
2364void
2365Options::parse_positional(std::string option)
2366{
2367 parse_positional(std::vector<std::string>{std::move(option)});
2368}
2369
2370inline
2371void
2372Options::parse_positional(std::vector<std::string> options)
2373{
2374 m_positional = std::move(options);
Jan Eilers7e989832020-06-19 11:47:21 +01002375
2376 m_positional_set.insert(m_positional.begin(), m_positional.end());
2377}
2378
2379inline
2380void
2381Options::parse_positional(std::initializer_list<std::string> options)
2382{
2383 parse_positional(std::vector<std::string>(options));
2384}
2385
2386inline
2387ParseResult
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002388Options::parse(int argc, const char* const* argv)
Jan Eilers7e989832020-06-19 11:47:21 +01002389{
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002390 OptionParser parser(*m_options, m_positional, m_allow_unrecognised);
2391
2392 return parser.parse(argc, argv);
Jan Eilers7e989832020-06-19 11:47:21 +01002393}
2394
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002395inline ParseResult
2396OptionParser::parse(int argc, const char* const* argv)
Jan Eilers7e989832020-06-19 11:47:21 +01002397{
2398 int current = 1;
Jan Eilers7e989832020-06-19 11:47:21 +01002399 bool consume_remaining = false;
Jim Flynn357add22023-04-10 23:26:40 +01002400 auto next_positional = m_positional.begin();
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002401
2402 std::vector<std::string> unmatched;
Jan Eilers7e989832020-06-19 11:47:21 +01002403
2404 while (current != argc)
2405 {
2406 if (strcmp(argv[current], "--") == 0)
2407 {
2408 consume_remaining = true;
2409 ++current;
2410 break;
2411 }
Jim Flynn357add22023-04-10 23:26:40 +01002412 bool matched = false;
2413 values::parser_tool::ArguDesc argu_desc =
2414 values::parser_tool::ParseArgument(argv[current], matched);
Jan Eilers7e989832020-06-19 11:47:21 +01002415
Jim Flynn357add22023-04-10 23:26:40 +01002416 if (!matched)
Jan Eilers7e989832020-06-19 11:47:21 +01002417 {
2418 //not a flag
2419
2420 // but if it starts with a `-`, then it's an error
2421 if (argv[current][0] == '-' && argv[current][1] != '\0') {
2422 if (!m_allow_unrecognised) {
Jim Flynn357add22023-04-10 23:26:40 +01002423 throw_or_mimic<exceptions::invalid_option_syntax>(argv[current]);
Jan Eilers7e989832020-06-19 11:47:21 +01002424 }
2425 }
2426
2427 //if true is returned here then it was consumed, otherwise it is
2428 //ignored
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002429 if (consume_positional(argv[current], next_positional))
Jan Eilers7e989832020-06-19 11:47:21 +01002430 {
2431 }
2432 else
2433 {
Jim Flynn357add22023-04-10 23:26:40 +01002434 unmatched.emplace_back(argv[current]);
Jan Eilers7e989832020-06-19 11:47:21 +01002435 }
2436 //if we return from here then it was parsed successfully, so continue
2437 }
2438 else
2439 {
2440 //short or long option?
Jim Flynn357add22023-04-10 23:26:40 +01002441 if (argu_desc.grouping)
Jan Eilers7e989832020-06-19 11:47:21 +01002442 {
Jim Flynn357add22023-04-10 23:26:40 +01002443 const std::string& s = argu_desc.arg_name;
Jan Eilers7e989832020-06-19 11:47:21 +01002444
2445 for (std::size_t i = 0; i != s.size(); ++i)
2446 {
2447 std::string name(1, s[i]);
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002448 auto iter = m_options.find(name);
Jan Eilers7e989832020-06-19 11:47:21 +01002449
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002450 if (iter == m_options.end())
Jan Eilers7e989832020-06-19 11:47:21 +01002451 {
2452 if (m_allow_unrecognised)
2453 {
Jim Flynn357add22023-04-10 23:26:40 +01002454 unmatched.push_back(std::string("-") + s[i]);
Jan Eilers7e989832020-06-19 11:47:21 +01002455 continue;
2456 }
2457 //error
Jim Flynn357add22023-04-10 23:26:40 +01002458 throw_or_mimic<exceptions::no_such_option>(name);
Jan Eilers7e989832020-06-19 11:47:21 +01002459 }
2460
2461 auto value = iter->second;
2462
2463 if (i + 1 == s.size())
2464 {
2465 //it must be the last argument
2466 checked_parse_arg(argc, argv, current, value, name);
2467 }
2468 else if (value->value().has_implicit())
2469 {
2470 parse_option(value, name, value->value().get_implicit_value());
2471 }
Jim Flynn357add22023-04-10 23:26:40 +01002472 else if (i + 1 < s.size())
2473 {
2474 std::string arg_value = s.substr(i + 1);
2475 parse_option(value, name, arg_value);
2476 break;
2477 }
Jan Eilers7e989832020-06-19 11:47:21 +01002478 else
2479 {
2480 //error
Jim Flynn357add22023-04-10 23:26:40 +01002481 throw_or_mimic<exceptions::option_requires_argument>(name);
Jan Eilers7e989832020-06-19 11:47:21 +01002482 }
2483 }
2484 }
Jim Flynn357add22023-04-10 23:26:40 +01002485 else if (argu_desc.arg_name.length() != 0)
Jan Eilers7e989832020-06-19 11:47:21 +01002486 {
Jim Flynn357add22023-04-10 23:26:40 +01002487 const std::string& name = argu_desc.arg_name;
Jan Eilers7e989832020-06-19 11:47:21 +01002488
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002489 auto iter = m_options.find(name);
Jan Eilers7e989832020-06-19 11:47:21 +01002490
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002491 if (iter == m_options.end())
Jan Eilers7e989832020-06-19 11:47:21 +01002492 {
2493 if (m_allow_unrecognised)
2494 {
2495 // keep unrecognised options in argument list, skip to next argument
Jim Flynn357add22023-04-10 23:26:40 +01002496 unmatched.emplace_back(argv[current]);
Jan Eilers7e989832020-06-19 11:47:21 +01002497 ++current;
2498 continue;
2499 }
2500 //error
Jim Flynn357add22023-04-10 23:26:40 +01002501 throw_or_mimic<exceptions::no_such_option>(name);
Jan Eilers7e989832020-06-19 11:47:21 +01002502 }
2503
2504 auto opt = iter->second;
2505
2506 //equals provided for long option?
Jim Flynn357add22023-04-10 23:26:40 +01002507 if (argu_desc.set_value)
Jan Eilers7e989832020-06-19 11:47:21 +01002508 {
2509 //parse the option given
2510
Jim Flynn357add22023-04-10 23:26:40 +01002511 parse_option(opt, name, argu_desc.value);
Jan Eilers7e989832020-06-19 11:47:21 +01002512 }
2513 else
2514 {
2515 //parse the next argument
2516 checked_parse_arg(argc, argv, current, opt, name);
2517 }
2518 }
2519
2520 }
2521
2522 ++current;
2523 }
2524
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002525 for (auto& opt : m_options)
Jan Eilers7e989832020-06-19 11:47:21 +01002526 {
2527 auto& detail = opt.second;
2528 const auto& value = detail->value();
2529
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002530 auto& store = m_parsed[detail->hash()];
Jan Eilers7e989832020-06-19 11:47:21 +01002531
Jim Flynn357add22023-04-10 23:26:40 +01002532 if (value.has_default()) {
2533 if (!store.count() && !store.has_default()) {
2534 parse_default(detail);
2535 }
2536 }
2537 else {
2538 parse_no_value(detail);
Jan Eilers7e989832020-06-19 11:47:21 +01002539 }
2540 }
2541
2542 if (consume_remaining)
2543 {
2544 while (current < argc)
2545 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002546 if (!consume_positional(argv[current], next_positional)) {
Jan Eilers7e989832020-06-19 11:47:21 +01002547 break;
2548 }
2549 ++current;
2550 }
2551
2552 //adjust argv for any that couldn't be swallowed
2553 while (current != argc) {
Jim Flynn357add22023-04-10 23:26:40 +01002554 unmatched.emplace_back(argv[current]);
Jan Eilers7e989832020-06-19 11:47:21 +01002555 ++current;
2556 }
2557 }
2558
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002559 finalise_aliases();
Jan Eilers7e989832020-06-19 11:47:21 +01002560
Jim Flynn357add22023-04-10 23:26:40 +01002561 ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(m_defaults), std::move(unmatched));
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002562 return parsed;
2563}
2564
2565inline
2566void
2567OptionParser::finalise_aliases()
2568{
2569 for (auto& option: m_options)
2570 {
2571 auto& detail = *option.second;
2572 auto hash = detail.hash();
2573 m_keys[detail.short_name()] = hash;
Jim Flynn357add22023-04-10 23:26:40 +01002574 for(const auto& long_name : detail.long_names()) {
2575 m_keys[long_name] = hash;
2576 }
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002577
2578 m_parsed.emplace(hash, OptionValue());
2579 }
Jan Eilers7e989832020-06-19 11:47:21 +01002580}
2581
2582inline
2583void
2584Options::add_option
2585(
2586 const std::string& group,
2587 const Option& option
2588)
2589{
2590 add_options(group, {option});
2591}
2592
2593inline
2594void
2595Options::add_option
2596(
2597 const std::string& group,
2598 const std::string& s,
Jim Flynn357add22023-04-10 23:26:40 +01002599 const OptionNames& l,
Jan Eilers7e989832020-06-19 11:47:21 +01002600 std::string desc,
2601 const std::shared_ptr<const Value>& value,
2602 std::string arg_help
2603)
2604{
2605 auto stringDesc = toLocalString(std::move(desc));
2606 auto option = std::make_shared<OptionDetails>(s, l, stringDesc, value);
2607
2608 if (!s.empty())
2609 {
2610 add_one_option(s, option);
2611 }
2612
Jim Flynn357add22023-04-10 23:26:40 +01002613 for(const auto& long_name : l) {
2614 add_one_option(long_name, option);
Jan Eilers7e989832020-06-19 11:47:21 +01002615 }
2616
Jan Eilers7e989832020-06-19 11:47:21 +01002617 //add the help details
2618 auto& options = m_help[group];
2619
2620 options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
2621 value->has_default(), value->get_default_value(),
2622 value->has_implicit(), value->get_implicit_value(),
2623 std::move(arg_help),
2624 value->is_container(),
2625 value->is_boolean()});
2626}
2627
2628inline
2629void
2630Options::add_one_option
2631(
2632 const std::string& option,
2633 const std::shared_ptr<OptionDetails>& details
2634)
2635{
2636 auto in = m_options->emplace(option, details);
2637
2638 if (!in.second)
2639 {
Jim Flynn357add22023-04-10 23:26:40 +01002640 throw_or_mimic<exceptions::option_already_exists>(option);
Jan Eilers7e989832020-06-19 11:47:21 +01002641 }
2642}
2643
2644inline
2645String
2646Options::help_one_group(const std::string& g) const
2647{
2648 using OptionHelp = std::vector<std::pair<String, String>>;
2649
2650 auto group = m_help.find(g);
2651 if (group == m_help.end())
2652 {
2653 return "";
2654 }
2655
2656 OptionHelp format;
2657
Jim Flynn357add22023-04-10 23:26:40 +01002658 std::size_t longest = 0;
Jan Eilers7e989832020-06-19 11:47:21 +01002659
2660 String result;
2661
2662 if (!g.empty())
2663 {
2664 result += toLocalString(" " + g + " options:\n");
2665 }
2666
2667 for (const auto& o : group->second.options)
2668 {
Jim Flynn357add22023-04-10 23:26:40 +01002669 if (o.l.size() &&
2670 m_positional_set.find(o.l.front()) != m_positional_set.end() &&
Jan Eilers7e989832020-06-19 11:47:21 +01002671 !m_show_positional)
2672 {
2673 continue;
2674 }
2675
2676 auto s = format_option(o);
2677 longest = (std::max)(longest, stringLength(s));
2678 format.push_back(std::make_pair(s, String()));
2679 }
Jim Flynn357add22023-04-10 23:26:40 +01002680 longest = (std::min)(longest, OPTION_LONGEST);
Jan Eilers7e989832020-06-19 11:47:21 +01002681
Jim Flynn357add22023-04-10 23:26:40 +01002682 //widest allowed description -- min 10 chars for helptext/line
2683 std::size_t allowed = 10;
2684 if (m_width > allowed + longest + OPTION_DESC_GAP)
2685 {
2686 allowed = m_width - longest - OPTION_DESC_GAP;
2687 }
Jan Eilers7e989832020-06-19 11:47:21 +01002688
2689 auto fiter = format.begin();
2690 for (const auto& o : group->second.options)
2691 {
Jim Flynn357add22023-04-10 23:26:40 +01002692 if (o.l.size() &&
2693 m_positional_set.find(o.l.front()) != m_positional_set.end() &&
Jan Eilers7e989832020-06-19 11:47:21 +01002694 !m_show_positional)
2695 {
2696 continue;
2697 }
2698
Jim Flynn357add22023-04-10 23:26:40 +01002699 auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion);
Jan Eilers7e989832020-06-19 11:47:21 +01002700
2701 result += fiter->first;
2702 if (stringLength(fiter->first) > longest)
2703 {
2704 result += '\n';
2705 result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));
2706 }
2707 else
2708 {
2709 result += toLocalString(std::string(longest + OPTION_DESC_GAP -
2710 stringLength(fiter->first),
2711 ' '));
2712 }
2713 result += d;
2714 result += '\n';
2715
2716 ++fiter;
2717 }
2718
2719 return result;
2720}
2721
2722inline
2723void
2724Options::generate_group_help
2725(
2726 String& result,
2727 const std::vector<std::string>& print_groups
2728) const
2729{
Jim Flynn357add22023-04-10 23:26:40 +01002730 for (std::size_t i = 0; i != print_groups.size(); ++i)
Jan Eilers7e989832020-06-19 11:47:21 +01002731 {
2732 const String& group_help_text = help_one_group(print_groups[i]);
2733 if (empty(group_help_text))
2734 {
2735 continue;
2736 }
2737 result += group_help_text;
2738 if (i < print_groups.size() - 1)
2739 {
2740 result += '\n';
2741 }
2742 }
2743}
2744
2745inline
2746void
2747Options::generate_all_groups_help(String& result) const
2748{
2749 std::vector<std::string> all_groups;
Jan Eilers7e989832020-06-19 11:47:21 +01002750
Jim Flynn357add22023-04-10 23:26:40 +01002751 std::transform(
2752 m_help.begin(),
2753 m_help.end(),
2754 std::back_inserter(all_groups),
2755 [] (const std::map<std::string, HelpGroupDetails>::value_type& group)
2756 {
2757 return group.first;
2758 }
2759 );
Jan Eilers7e989832020-06-19 11:47:21 +01002760
2761 generate_group_help(result, all_groups);
2762}
2763
2764inline
2765std::string
Jim Flynn357add22023-04-10 23:26:40 +01002766Options::help(const std::vector<std::string>& help_groups, bool print_usage) const
Jan Eilers7e989832020-06-19 11:47:21 +01002767{
Jim Flynn357add22023-04-10 23:26:40 +01002768 String result = m_help_string;
2769 if(print_usage)
2770 {
2771 result+= "\nUsage:\n " + toLocalString(m_program);
2772 }
2773
2774 if (!m_custom_help.empty())
2775 {
2776 result += " " + toLocalString(m_custom_help);
2777 }
Jan Eilers7e989832020-06-19 11:47:21 +01002778
2779 if (!m_positional.empty() && !m_positional_help.empty()) {
2780 result += " " + toLocalString(m_positional_help);
2781 }
2782
2783 result += "\n\n";
2784
2785 if (help_groups.empty())
2786 {
2787 generate_all_groups_help(result);
2788 }
2789 else
2790 {
2791 generate_group_help(result, help_groups);
2792 }
2793
2794 return toUTF8String(result);
2795}
2796
2797inline
2798std::vector<std::string>
2799Options::groups() const
2800{
2801 std::vector<std::string> g;
2802
2803 std::transform(
2804 m_help.begin(),
2805 m_help.end(),
2806 std::back_inserter(g),
2807 [] (const std::map<std::string, HelpGroupDetails>::value_type& pair)
2808 {
2809 return pair.first;
2810 }
2811 );
2812
2813 return g;
2814}
2815
2816inline
2817const HelpGroupDetails&
2818Options::group_help(const std::string& group) const
2819{
2820 return m_help.at(group);
2821}
2822
2823} // namespace cxxopts
2824
2825#endif //CXXOPTS_HPP_INCLUDED