• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

STEllAR-GROUP / hpx / #856

28 Dec 2022 02:00AM UTC coverage: 86.602% (+0.05%) from 86.55%
#856

push

StellarBot
Merge #6119

6119: Update CMakeLists.txt r=hkaiser a=khuck

updating the default APEX version


Co-authored-by: Kevin Huck <khuck@cs.uoregon.edu>

174566 of 201573 relevant lines covered (86.6%)

1876093.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.52
/libs/core/program_options/src/options_description.cpp
1
// Copyright Vladimir Prus 2002-2004.
2
// Copyright Bertolt Mildner 2004.
3
//  SPDX-License-Identifier: BSL-1.0
4
// Distributed under the Boost Software License, Version 1.0.
5
// (See accompanying file LICENSE_1_0.txt
6
// or copy at http://www.boost.org/LICENSE_1_0.txt)
7

8
#include <hpx/program_options/config.hpp>
9
#include <hpx/assert.hpp>
10
#include <hpx/program_options/options_description.hpp>
11
// FIXME: this is only to get multiple_occurrences class
12
// should move that to a separate headers.
13
#include <hpx/program_options/parsers.hpp>
14
#include <hpx/string_util/tokenizer.hpp>
15

16
#include <climits>
17
#include <cstdarg>
18
#include <cstddef>
19
#include <cstring>
20
#include <iterator>
21
#include <memory>
22
#include <sstream>
23
#include <string>
24
#include <utility>
25
#include <vector>
26

27
namespace hpx::program_options {
28

29
    namespace {
30

31
        template <typename Char>
32
        std::basic_string<Char> tolower_(std::basic_string<Char> const& str)
110✔
33
        {
34
            std::basic_string<Char> result;
110✔
35
            for (typename std::basic_string<Char>::size_type i = 0;
350✔
36
                 i < str.size(); ++i)
350✔
37
            {
38
                result.append(1, static_cast<Char>(std::tolower(str[i])));
240✔
39
            }
240✔
40
            return result;
110✔
41
        }
110✔
42

43
    }    // unnamed namespace
44

45
    option_description::option_description() {}
×
46

47
    option_description::option_description(
95✔
48
        char const* names, value_semantic const* s)
49
      : m_value_semantic(s)
95✔
50
    {
95✔
51
        this->set_names(names);
95✔
52
    }
95✔
53

54
    option_description::option_description(
184,130✔
55
        char const* names, value_semantic const* s, char const* description)
56
      : m_description(description)
184,130✔
57
      , m_value_semantic(s)
184,130✔
58
    {
184,130✔
59
        this->set_names(names);
184,130✔
60
    }
184,130✔
61

62
    option_description::~option_description() {}
368,450✔
63

64
    option_description::match_result option_description::match(
1,888,052✔
65
        std::string const& option, bool approx, bool long_ignore_case,
66
        bool short_ignore_case) const
67
    {
68
        match_result result = no_match;
1,888,052✔
69
        std::string local_option =
70
            (long_ignore_case ? tolower_(option) : option);
1,888,052✔
71

72
        for (auto const& long_name : m_long_names)
3,736,909✔
73
        {
74
            std::string local_long_name(
75
                (long_ignore_case ? tolower_(long_name) : long_name));
1,887,957✔
76

77
            if (!local_long_name.empty())
1,887,957✔
78
            {
79
                if ((result == no_match) && (*local_long_name.rbegin() == '*'))
1,887,957✔
80
                {
81
                    // The name ends with '*'. Any specified name with the given
82
                    // prefix is OK.
83
                    if (local_option.find(local_long_name.substr(
47✔
84
                            0, local_long_name.length() - 1)) == 0)
47✔
85
                        result = approximate_match;
7✔
86
                }
47✔
87

88
                if (local_long_name == local_option)
1,887,957✔
89
                {
90
                    result = full_match;
39,100✔
91
                    break;
39,100✔
92
                }
93
                else if (approx)
1,848,857✔
94
                {
95
                    if (local_long_name.find(local_option) == 0)
1,205,489✔
96
                    {
97
                        result = approximate_match;
3,403✔
98
                    }
3,403✔
99
                }
1,205,489✔
100
            }
1,848,857✔
101
        }
1,887,957✔
102

103
        if (result != full_match)
1,888,052✔
104
        {
105
            std::string local_short_name(
106
                short_ignore_case ? tolower_(m_short_name) : m_short_name);
1,848,952✔
107

108
            if (local_short_name == local_option)
1,848,952✔
109
            {
110
                result = full_match;
163✔
111
            }
163✔
112
        }
1,848,952✔
113

114
        return result;
1,888,052✔
115
    }
1,888,052✔
116

117
    std::string const& option_description::key(std::string const& option) const
240,379✔
118
    {
119
        // We make the arbitrary choice of using the first long
120
        // name as the key, regardless of anything else
121
        if (!m_long_names.empty())
240,379✔
122
        {
123
            std::string const& first_long_name = *m_long_names.begin();
240,249✔
124
            if (first_long_name.find('*') != std::string::npos)
240,249✔
125
                // The '*' character means we're long_name
126
                // matches only part of the input. So, returning
127
                // long name will remove some of the information,
128
                // and we have to return the option as specified
129
                // in the source.
130
                return option;
9✔
131
            else
132
                return first_long_name;
240,240✔
133
        }
134
        else
135
            return m_short_name;
130✔
136
    }
240,379✔
137

138
    std::string option_description::canonical_display_name(
15✔
139
        int prefix_style) const
140
    {
141
        // We prefer the first long name over any others
142
        if (!m_long_names.empty())
15✔
143
        {
144
            if (prefix_style == command_line_style::allow_long)
15✔
145
                return "--" + *m_long_names.begin();
13✔
146
            if (prefix_style == command_line_style::allow_long_disguise)
2✔
147
                return "-" + *m_long_names.begin();
×
148
        }
2✔
149
        // sanity check: m_short_name[0] should be '-' or '/'
150
        if (m_short_name.length() == 2)
2✔
151
        {
152
            if (prefix_style == command_line_style::allow_slash_for_short)
2✔
153
                return std::string("/") + m_short_name[1];
×
154
            if (prefix_style == command_line_style::allow_dash_for_short)
2✔
155
                return std::string("-") + m_short_name[1];
×
156
        }
2✔
157
        if (!m_long_names.empty())
2✔
158
            return *m_long_names.begin();
2✔
159
        else
160
            return m_short_name;
×
161
    }
15✔
162

163
    std::string const& option_description::long_name() const
105✔
164
    {
165
        static std::string empty_string("");
105✔
166
        return m_long_names.empty() ? empty_string : *m_long_names.begin();
105✔
167
    }
×
168

169
    std::pair<std::string const*, std::size_t> option_description::long_names()
8✔
170
        const
171
    {
172
        // reinterpret_cast is to please msvc 10.
173
        return (m_long_names.empty()) ?
8✔
174
            std::pair<std::string const*, size_t>(
×
175
                reinterpret_cast<std::string const*>(0), 0) :
×
176
            std::pair<std::string const*, size_t>(
8✔
177
                &(*m_long_names.begin()), m_long_names.size());
8✔
178
    }
179

180
    option_description& option_description::set_names(char const* _names)
184,225✔
181
    {
182
        m_long_names.clear();
184,225✔
183
        std::istringstream iss(_names);
184,225✔
184
        std::string name;
184,225✔
185

186
        while (std::getline(iss, name, ','))
368,947✔
187
        {
188
            m_long_names.push_back(name);
184,722✔
189
        }
190
        HPX_ASSERT(!m_long_names.empty() && "No option names were specified");
184,225✔
191

192
        bool try_interpreting_last_name_as_a_switch = m_long_names.size() > 1;
184,225✔
193
        if (try_interpreting_last_name_as_a_switch)
184,225✔
194
        {
195
            std::string const& last_name = *m_long_names.rbegin();
489✔
196
            if (last_name.length() == 1)
489✔
197
            {
198
                m_short_name = '-' + last_name;
478✔
199
                m_long_names.pop_back();
478✔
200
                // The following caters to the (valid) input of ",c" for some
201
                // character c, where the caller only wants this option to have
202
                // a short name.
203
                if (m_long_names.size() == 1 && (*m_long_names.begin()).empty())
478✔
204
                {
205
                    m_long_names.clear();
74✔
206
                }
74✔
207
            }
478✔
208
        }
489✔
209
        // We could theoretically also ensure no remaining long names
210
        // are empty, or that none of them have length 1
211
        return *this;
212
    }
184,225✔
213

214
    std::string const& option_description::description() const
192✔
215
    {
216
        return m_description;
192✔
217
    }
218

219
    std::shared_ptr<value_semantic const> option_description::semantic() const
455,384✔
220
    {
221
        return m_value_semantic;
455,384✔
222
    }
223

224
    std::string option_description::format_name() const
273✔
225
    {
226
        if (!m_short_name.empty())
273✔
227
        {
228
            return m_long_names.empty() ? m_short_name :
4✔
229
                                          std::string(m_short_name)
2✔
230
                                              .append(" [ --")
2✔
231
                                              .append(*m_long_names.begin())
2✔
232
                                              .append(" ]");
2✔
233
        }
234
        return std::string("--").append(*m_long_names.begin());
271✔
235
    }
273✔
236

237
    std::string option_description::format_parameter() const
273✔
238
    {
239
        if (m_value_semantic->max_tokens() != 0)
273✔
240
            return m_value_semantic->name();
193✔
241
        else
242
            return "";
80✔
243
    }
273✔
244

245
    options_description_easy_init::options_description_easy_init(
34,431✔
246
        options_description* owner)
247
      : owner(owner)
34,431✔
248
    {
249
    }
34,431✔
250

251
    options_description_easy_init& options_description_easy_init::operator()(
68,961✔
252
        char const* name, char const* description)
253
    {
254
        // Create untyped semantic which accepts zero tokens: i.e.
255
        // no value can be specified on command line.
256
        // FIXME: does not look exception-safe
257
        std::shared_ptr<option_description> d(
68,961✔
258
            new option_description(name, new untyped_value(true), description));
68,961✔
259

260
        owner->add(d);
68,961✔
261
        return *this;
262
    }
68,961✔
263

264
    options_description_easy_init& options_description_easy_init::operator()(
95✔
265
        char const* name, value_semantic const* s)
266
    {
267
        std::shared_ptr<option_description> d(new option_description(name, s));
95✔
268
        owner->add(d);
95✔
269
        return *this;
270
    }
95✔
271

272
    options_description_easy_init& options_description_easy_init::operator()(
115,169✔
273
        char const* name, value_semantic const* s, char const* description)
274
    {
275
        std::shared_ptr<option_description> d(
115,169✔
276
            new option_description(name, s, description));
115,169✔
277

278
        owner->add(d);
115,169✔
279
        return *this;
280
    }
115,169✔
281

282
    options_description::options_description(
9,016✔
283
        unsigned line_length, unsigned min_description_length)
284
      : m_line_length(line_length)
9,016✔
285
      , m_min_description_length(min_description_length)
9,016✔
286
    {
287
        // we require a space between the option and description parts, so add 1.
288
        HPX_ASSERT(m_min_description_length < m_line_length - 1);
9,016✔
289
    }
9,016✔
290

291
    options_description::options_description(std::string const& caption,
31,525✔
292
        unsigned line_length, unsigned min_description_length)
293
      : m_caption(caption)
31,525✔
294
      , m_line_length(line_length)
31,525✔
295
      , m_min_description_length(min_description_length)
31,525✔
296
    {
297
        // we require a space between the option and description parts, so add 1.
298
        HPX_ASSERT(m_min_description_length < m_line_length - 1);
31,525✔
299
    }
31,525✔
300

301
    void options_description::add(std::shared_ptr<option_description> desc)
544,965✔
302
    {
303
        m_options.push_back(desc);
544,965✔
304
        belong_to_group.push_back(false);
544,965✔
305
    }
544,965✔
306

307
    options_description& options_description::add(
58,544✔
308
        options_description const& desc)
309
    {
310
        std::shared_ptr<options_description> d(new options_description(desc));
58,544✔
311
        groups.push_back(d);
58,544✔
312

313
        for (auto const& option : desc.m_options)
419,284✔
314
        {
315
            add(option);
360,740✔
316
            belong_to_group.back() = true;
360,740✔
317
        }
318

319
        return *this;
320
    }
58,544✔
321

322
    options_description_easy_init options_description::add_options()
34,431✔
323
    {
324
        return options_description_easy_init(this);
34,431✔
325
    }
326

327
    option_description const& options_description::find(std::string const& name,
14,030✔
328
        bool approx, bool long_ignore_case, bool short_ignore_case) const
329
    {
330
        option_description const* d =
14,030✔
331
            find_nothrow(name, approx, long_ignore_case, short_ignore_case);
14,030✔
332
        if (!d)
14,030✔
333
            throw unknown_option();
×
334
        return *d;
14,030✔
335
    }
×
336

337
    std::vector<std::shared_ptr<option_description>> const&
338
    options_description::options() const
4,312✔
339
    {
340
        return m_options;
4,312✔
341
    }
342

343
    option_description const* options_description::find_nothrow(
40,556✔
344
        std::string const& name, bool approx, bool long_ignore_case,
345
        bool short_ignore_case) const
346
    {
347
        std::shared_ptr<option_description> found;
40,556✔
348
        bool had_full_match = false;
40,556✔
349
        std::vector<std::string> approximate_matches;
40,556✔
350
        std::vector<std::string> full_matches;
40,556✔
351

352
        // We use linear search because matching specified option
353
        // name with the declared option name need to take care about
354
        // case sensitivity and trailing '*' and so we can't use simple map.
355
        for (auto const& option : m_options)
1,928,608✔
356
        {
357
            option_description::match_result r = option->match(
3,776,104✔
358
                name, approx, long_ignore_case, short_ignore_case);
1,888,052✔
359

360
            if (r == option_description::no_match)
1,888,052✔
361
                continue;
1,845,379✔
362

363
            if (r == option_description::full_match)
42,673✔
364
            {
365
                full_matches.push_back(option->key(name));
39,263✔
366
                found = option;
39,263✔
367
                had_full_match = true;
39,263✔
368
            }
39,263✔
369
            else
370
            {
371
                // FIXME: the use of 'key' here might not
372
                // be the best approach.
373
                approximate_matches.push_back(option->key(name));
3,410✔
374
                if (!had_full_match)
3,410✔
375
                    found = option;
3,407✔
376
            }
377
        }
378
        if (full_matches.size() > 1)
40,556✔
379
            throw ambiguous_option(full_matches);
3✔
380

381
        // If we have a full match, and an approximate match,
382
        // ignore approximate match instead of reporting error.
383
        // Say, if we have options "all" and "all-chroots", then
384
        // "--all" on the command line should select the first one,
385
        // without ambiguity.
386
        if (full_matches.empty() && approximate_matches.size() > 1)
40,553✔
387
            throw ambiguous_option(approximate_matches);
4✔
388

389
        return found.get();
40,549✔
390
    }
40,556✔
391

392
    HPX_CORE_EXPORT
393
    std::ostream& operator<<(std::ostream& os, options_description const& desc)
11✔
394
    {
395
        desc.print(os);
11✔
396
        return os;
11✔
397
    }
398

399
    namespace {
400

401
        /* Given a string 'par', that contains no newline characters
402
           outputs it to 'os' with wordwrapping, that is, as several
403
           line.
404

405
           Each output line starts with 'indent' space characters,
406
           following by characters from 'par'. The total length of
407
           line is no longer than 'line_length'.
408

409
        */
410
        void format_paragraph(std::ostream& os, std::string par,
105✔
411
            std::size_t indent, std::size_t line_length)
412
        {
413
            // Through reminder of this function, 'line_length' will
414
            // be the length available for characters, not including
415
            // indent.
416
            HPX_ASSERT(indent < line_length);
105✔
417
            line_length -= indent;
105✔
418

419
            // index of tab (if present) is used as additional indent relative
420
            // to first_column_width if paragrapth is spanned over multiple
421
            // lines if tab is not on first line it is ignored
422
            std::string::size_type par_indent = par.find('\t');
105✔
423

424
            if (par_indent == std::string::npos)
105✔
425
            {
426
                par_indent = 0;
101✔
427
            }
101✔
428
            else
429
            {
430
                // only one tab per paragraph allowed
431
                if (count(par.begin(), par.end(), '\t') > 1)
4✔
432
                {
433
                    throw program_options::error(
×
434
                        "Only one tab per paragraph is allowed in the options "
×
435
                        "description");
436
                }
437

438
                // erase tab from string
439
                par.erase(par_indent, 1);
4✔
440

441
                // this HPX_ASSERT may fail due to user error or
442
                // environment conditions!
443
                HPX_ASSERT(par_indent < line_length);
4✔
444

445
                // ignore tab if not on first line
446
                if (par_indent >= line_length)
4✔
447
                {
448
                    par_indent = 0;
×
449
                }
×
450
            }
451

452
            if (par.size() < line_length)
105✔
453
            {
454
                os << par;
22✔
455
            }
22✔
456
            else
457
            {
458
                std::string::const_iterator line_begin = par.begin();
83✔
459
                std::string::const_iterator const par_end = par.end();
83✔
460

461
                bool first_line = true;    // of current paragraph!
83✔
462

463
                while (line_begin < par_end)    // paragraph lines
400✔
464
                {
465
                    if (!first_line)
317✔
466
                    {
467
                        // If line starts with space, but second character
468
                        // is not space, remove the leading space.
469
                        // We don't remove double spaces because those
470
                        // might be intentianal.
471
                        if ((*line_begin == ' ') &&
262✔
472
                            ((line_begin + 1 < par_end) &&
28✔
473
                                (*(line_begin + 1) != ' ')))
28✔
474
                        {
475
                            line_begin += 1;    // line_begin != line_end
28✔
476
                        }
28✔
477
                    }
234✔
478

479
                    // Take care to never increment the iterator past
480
                    // the end, since MSVC 8.0 (brokenly), assumes that
481
                    // doing that, even if no access happens, is a bug.
482
                    unsigned remaining = static_cast<unsigned>(
317✔
483
                        std::distance(line_begin, par_end));
317✔
484
                    std::string::const_iterator line_end = line_begin +
317✔
485
                        ((remaining < line_length) ? remaining : line_length);
317✔
486

487
                    // prevent chopped words
488
                    // Is line_end between two non-space characters?
489
                    if ((*(line_end - 1) != ' ') &&
595✔
490
                        ((line_end < par_end) && (*line_end != ' ')))
278✔
491
                    {
492
                        // find last ' ' in the second half of the current paragraph line
493
                        std::string::const_iterator last_space = find(
167✔
494
                            std::reverse_iterator<std::string::const_iterator>(
167✔
495
                                line_end),
167✔
496
                            std::reverse_iterator<std::string::const_iterator>(
167✔
497
                                line_begin),
167✔
498
                            ' ')
167✔
499
                                                                     .base();
167✔
500

501
                        if (last_space != line_begin)
167✔
502
                        {
503
                            // is last_space within the second half ot the
504
                            // current line
505
                            if (static_cast<unsigned>(std::distance(
324✔
506
                                    last_space, line_end)) < (line_length / 2))
324✔
507
                            {
508
                                line_end = last_space;
155✔
509
                            }
155✔
510
                        }
162✔
511
                    }    // prevent chopped words
167✔
512

513
                    // write line to stream
514
                    copy(line_begin, line_end, std::ostream_iterator<char>(os));
317✔
515

516
                    if (first_line)
317✔
517
                    {
518
                        indent += static_cast<unsigned>(par_indent);
83✔
519
                        line_length -= static_cast<unsigned>(
83✔
520
                            par_indent);    // there's less to work with now
83✔
521
                        first_line = false;
83✔
522
                    }
83✔
523

524
                    // more lines to follow?
525
                    if (line_end != par_end)
317✔
526
                    {
527
                        os << '\n';
234✔
528

529
                        for (std::size_t pad = indent; pad > 0; --pad)
9,439✔
530
                        {
531
                            os.put(' ');
9,205✔
532
                        }
9,205✔
533
                    }
234✔
534

535
                    // next line starts after of this line
536
                    line_begin = line_end;
317✔
537
                }    // paragraph lines
538
            }
539
        }
105✔
540

541
        void format_description(std::ostream& os, std::string const& desc,
96✔
542
            std::size_t first_column_width, std::size_t line_length)
543
        {
544
            // we need to use one char less per line to work correctly if actual
545
            // console has longer lines
546
            HPX_ASSERT(line_length > 1);
96✔
547
            if (line_length > 1)
96✔
548
            {
549
                --line_length;
96✔
550
            }
96✔
551

552
            // line_length must be larger than first_column_width
553
            // this HPX_ASSERT may fail due to user error or environment
554
            // conditions!
555
            HPX_ASSERT(line_length > first_column_width);
96✔
556

557
            hpx::string_util::tokenizer paragraphs(desc,
192✔
558
                hpx::string_util::char_separator(
96✔
559
                    "\n", "", hpx::string_util::empty_token_policy::keep));
560

561
            auto par_iter = paragraphs.begin();
96✔
562
            auto const par_end = paragraphs.end();
96✔
563

564
            while (par_iter != par_end)    // paragraphs
201✔
565
            {
566
                format_paragraph(
105✔
567
                    os, *par_iter, first_column_width, line_length);
105✔
568

569
                ++par_iter;
105✔
570

571
                // prepare next line if any
572
                if (par_iter != par_end)
105✔
573
                {
574
                    os << '\n';
9✔
575

576
                    for (std::size_t pad = first_column_width; pad > 0; --pad)
241✔
577
                    {
578
                        os.put(' ');
232✔
579
                    }
232✔
580
                }
9✔
581
            }    // paragraphs
582
        }
96✔
583

584
        void format_one(std::ostream& os, option_description const& opt,
96✔
585
            std::size_t first_column_width, std::size_t line_length)
586
        {
587
            std::stringstream ss;
96✔
588
            ss << "  " << opt.format_name() << ' ' << opt.format_parameter();
96✔
589

590
            // Don't use ss.rdbuf() since g++ 2.96 is buggy on it.
591
            os << ss.str();
96✔
592

593
            if (!opt.description().empty())
96✔
594
            {
595
                if (ss.str().size() >= first_column_width)
96✔
596
                {
597
                    // first column is too long, lets put description in new line
598
                    os.put('\n');
6✔
599
                    for (std::size_t pad = first_column_width; pad > 0; --pad)
216✔
600
                    {
601
                        os.put(' ');
210✔
602
                    }
210✔
603
                }
6✔
604
                else
605
                {
606
                    for (std::size_t pad = first_column_width - ss.str().size();
1,640✔
607
                         pad > 0; --pad)
1,640✔
608
                    {
609
                        os.put(' ');
1,550✔
610
                    }
1,550✔
611
                }
612

613
                format_description(
96✔
614
                    os, opt.description(), first_column_width, line_length);
96✔
615
            }
96✔
616
        }
96✔
617
    }    // namespace
618

619
    std::size_t options_description::get_option_column_width() const
26✔
620
    {
621
        /* Find the maximum width of the option column */
622
        std::size_t width(23);
26✔
623
        std::size_t i;    // vc6 has broken for loop scoping
624
        for (i = 0; i < m_options.size(); ++i)
203✔
625
        {
626
            option_description const& opt = *m_options[i];
177✔
627
            std::stringstream ss;
177✔
628
            ss << "  " << opt.format_name() << ' ' << opt.format_parameter();
177✔
629
            width = (std::max)(width, ss.str().size());
177✔
630
        }
177✔
631

632
        /* Get width of groups as well*/
633
        for (auto const& group : groups)
41✔
634
            width = (std::max)(width, group->get_option_column_width());
15✔
635

636
        /* this is the column were description should start, if first
637
           column is longer, we go to a new line */
638
        std::size_t const start_of_description_column =
26✔
639
            m_line_length - m_min_description_length;
26✔
640

641
        width = (std::min)(width, start_of_description_column - 1);
26✔
642

643
        /* add an additional space to improve readability */
644
        ++width;
26✔
645
        return width;
26✔
646
    }
×
647

648
    void options_description::print(std::ostream& os, std::size_t width) const
26✔
649
    {
650
        if (!m_caption.empty())
26✔
651
            os << m_caption << ":\n";
18✔
652

653
        if (!width)
26✔
654
            width = get_option_column_width();
11✔
655

656
        /* The options formatting style is stolen from Subversion. */
657
        for (std::size_t i = 0; i < m_options.size(); ++i)
203✔
658
        {
659
            if (belong_to_group[i])
177✔
660
                continue;
81✔
661

662
            option_description const& opt = *m_options[i];
96✔
663

664
            format_one(os, opt, width, m_line_length);
96✔
665

666
            os << "\n";
96✔
667
        }
96✔
668

669
        for (auto const& group : groups)
41✔
670
        {
671
            os << "\n";
15✔
672
            group->print(os, width);
15✔
673
        }
674
    }
26✔
675
}    // namespace hpx::program_options
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc