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

STEllAR-GROUP / hpx / #882

31 Aug 2023 07:44PM UTC coverage: 41.798% (-44.7%) from 86.546%
#882

push

19442 of 46514 relevant lines covered (41.8%)

126375.38 hits per line

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

39.3
/libs/core/program_options/src/options_description.cpp
1
//  Copyright Vladimir Prus 2002-2004.
2
//  Copyright Bertolt Mildner 2004.
3
//
4
//  SPDX-License-Identifier: BSL-1.0
5
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
6
//  file LICENSE_1_0.txt 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/modules/string_util.hpp>
11
#include <hpx/program_options/cmdline.hpp>
12
#include <hpx/program_options/errors.hpp>
13
#include <hpx/program_options/options_description.hpp>
14

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

26
namespace hpx::program_options {
27

28
    namespace {
29

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

42
    }    // unnamed namespace
43

44
    option_description::option_description() {}
45

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

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

8,949✔
61
    option_description::~option_description() {}
62

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

65,681✔
71
        for (auto const& long_name : m_long_names)
72
        {
130,007✔
73
            std::string local_long_name(
74
                long_ignore_case ? tolower_(long_name) : long_name);
75

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

87
                if (local_long_name == local_option)
88
                {
65,681✔
89
                    result = full_match;
90
                    break;
91
                }
92
                if (approx)
93
                {
64,326✔
94
                    if (local_long_name.find(local_option) == 0)
95
                    {
41,656✔
96
                        result = approximate_match;
97
                    }
98
                }
99
            }
100
        }
101

102
        if (result != full_match)
103
        {
104
            std::string const local_short_name(
105
                short_ignore_case ? tolower_(m_short_name) : m_short_name);
106

64,326✔
107
            if (local_short_name == local_option)
108
            {
64,326✔
109
                result = full_match;
110
            }
111
        }
112

113
        return result;
114
    }
65,681✔
115

116
    std::string const& option_description::key(std::string const& option) const
117
    {
10,834✔
118
        // We make the arbitrary choice of using the first long
119
        // name as the key, regardless of anything else
120
        if (!m_long_names.empty())
121
        {
10,834✔
122
            std::string const& first_long_name = *m_long_names.begin();
123
            if (first_long_name.find('*') != std::string::npos)
124
            {
10,834✔
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;    // NOLINT(bugprone-return-const-ref-from-parameter)
131
            }
132
            return first_long_name;
10,834✔
133
        }
134
        else
135
            return m_short_name;
×
136
    }
137

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

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

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

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

186
        while (std::getline(iss, name, ','))
17,904✔
187
        {
188
            m_long_names.push_back(name);
8,955✔
189
        }
190
        HPX_ASSERT(!m_long_names.empty() && "No option names were specified");
191

192
        if (m_long_names.size() > 1)
8,949✔
193
        {
194
            std::string const& last_name = *m_long_names.rbegin();
195
            if (last_name.length() == 1)
6✔
196
            {
197
                m_short_name = '-' + last_name;
12✔
198
                m_long_names.pop_back();
199

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())
6✔
204
                {
205
                    m_long_names.clear();
206
                }
207
            }
208
        }
209
        // We could theoretically also ensure no remaining long names
210
        // are empty, or that none of them have length 1
211
        return *this;
8,949✔
212
    }
8,949✔
213

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

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

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

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

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

251
    options_description_easy_init& options_description_easy_init::operator()(
3,427✔
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 =
258
            std::make_shared<option_description>(
259
                name, new untyped_value(true), description);
3,427✔
260

261
        owner->add(HPX_MOVE(d));
3,427✔
262
        return *this;
3,427✔
263
    }
264

265
    options_description_easy_init& options_description_easy_init::operator()(
×
266
        char const* name, value_semantic const* s)
267
    {
268
        std::shared_ptr<option_description> d =
269
            std::make_shared<option_description>(name, s);
270
        owner->add(HPX_MOVE(d));
×
271
        return *this;
×
272
    }
273

274
    options_description_easy_init& options_description_easy_init::operator()(
5,522✔
275
        char const* name, value_semantic const* s, char const* description)
276
    {
277
        std::shared_ptr<option_description> d =
278
            std::make_shared<option_description>(name, s, description);
279

280
        owner->add(HPX_MOVE(d));
5,522✔
281
        return *this;
5,522✔
282
    }
283

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

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

303
    void options_description::add(std::shared_ptr<option_description> desc)
26,778✔
304
    {
305
        m_options.push_back(HPX_MOVE(desc));
26,778✔
306
        belong_to_group.push_back(0);
26,778✔
307
    }
26,778✔
308

309
    options_description& options_description::add(
2,762✔
310
        options_description const& desc)
311
    {
312
        groups.push_back(std::make_shared<options_description>(desc));
5,524✔
313

314
        for (auto const& option : desc.m_options)
20,591✔
315
        {
316
            add(option);
35,658✔
317
            belong_to_group.back() = 1;
17,829✔
318
        }
319

320
        return *this;
2,762✔
321
    }
322

323
    options_description_easy_init options_description::add_options()
1,643✔
324
    {
325
        return options_description_easy_init(this);
1,643✔
326
    }
327

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

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

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

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

361
            if (r == option_description::no_match)
65,681✔
362
                continue;
64,326✔
363

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

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

388
        return found.get();
1,361✔
389
    }
1,361✔
390

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

398
    namespace {
399

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

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

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

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

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

437
                // erase tab from string
438
                par.erase(par_indent, 1);
×
439

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

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

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

460
                bool first_line = true;    // of current paragraph!
461

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

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

486
                    // prevent chopped words
487
                    // Is line_end between two non-space characters?
×
488
                    if (*(line_end - 1) != ' ' &&
×
489
                        (line_end < par_end && *line_end != ' '))
490
                    {
491
                        // find last ' ' in the second half of the current paragraph line
×
492
                        auto last_space = find(std::reverse_iterator(line_end),
×
493
                            std::reverse_iterator(line_begin), ' ')
494
                                              .base();
495

×
496
                        if (last_space != line_begin)
497
                        {
498
                            // is last_space within the second half ot the
499
                            // current line
×
500
                            if (static_cast<std::size_t>(std::distance(
×
501
                                    last_space, line_end)) < line_length / 2)
502
                            {
503
                                line_end = last_space;
504
                            }
505
                        }
506
                    }    // prevent chopped words
507

508
                    // write line to stream
509
                    copy(line_begin, line_end, std::ostream_iterator<char>(os));
510

×
511
                    if (first_line)
512
                    {
×
513
                        indent += par_indent;    //-V101
514

515
                        // there's less to work with now
×
516
                        line_length -= par_indent;
517
                        first_line = false;
518
                    }
519

520
                    // more lines to follow?
×
521
                    if (line_end != par_end)
522
                    {
×
523
                        os << '\n';
524

×
525
                        for (std::size_t pad = indent; pad > 0; --pad)
526
                        {
×
527
                            os.put(' ');
528
                        }
529
                    }
530

531
                    // next line starts after of this line
532
                    line_begin = line_end;
533
                }    // paragraph lines
534
            }
×
535
        }
536

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

548
            // line_length must be larger than first_column_width
549
            // this HPX_ASSERT may fail due to user error or environment
550
            // conditions!
551
            HPX_ASSERT(line_length > first_column_width);
552

553
            hpx::string_util::tokenizer paragraphs(desc,
×
554
                hpx::string_util::char_separator(
555
                    "\n", "", hpx::string_util::empty_token_policy::keep));
556

557
            auto par_iter = paragraphs.begin();
558
            auto const par_end = paragraphs.end();
559

×
560
            while (par_iter != par_end)    // paragraphs
561
            {
×
562
                format_paragraph(
563
                    os, *par_iter, first_column_width, line_length);
564

565
                ++par_iter;
566

567
                // prepare next line if any
×
568
                if (par_iter != par_end)
569
                {
×
570
                    os << '\n';
571

×
572
                    for (std::size_t pad = first_column_width; pad > 0; --pad)
573
                    {
×
574
                        os.put(' ');
575
                    }
576
                }
577
            }    // paragraphs
×
578
        }
579

×
580
        void format_one(std::ostream& os, option_description const& opt,
581
            std::size_t first_column_width, std::size_t line_length)
582
        {
×
583
            std::stringstream ss;
×
584
            ss << "  " << opt.format_name() << ' ' << opt.format_parameter();
585

586
            // Don't use ss.rdbuf() since g++ 2.96 is buggy on it.
×
587
            os << ss.str();
588

×
589
            if (!opt.description().empty())
590
            {
×
591
                if (ss.str().size() >= first_column_width)
592
                {
593
                    // first column is too long, lets put description in new line
×
594
                    os.put('\n');
×
595
                    for (std::size_t pad = first_column_width; pad > 0; --pad)
596
                    {
×
597
                        os.put(' ');
598
                    }
599
                }
600
                else
601
                {
×
602
                    for (std::size_t pad = first_column_width - ss.str().size();
×
603
                        pad > 0; --pad)
604
                    {
×
605
                        os.put(' ');
606
                    }
607
                }
608

×
609
                format_description(
610
                    os, opt.description(), first_column_width, line_length);
611
            }
×
612
        }
613
    }    // namespace
614

×
615
    std::size_t options_description::get_option_column_width() const
616
    {
617
        /* Find the maximum width of the option column */
×
618
        std::size_t width(23);
×
619
        for (std::size_t i = 0; i < m_options.size(); ++i)
620
        {
621
            option_description const& opt = *m_options[i];
×
622
            std::stringstream ss;
×
623
            ss << "  " << opt.format_name() << ' ' << opt.format_parameter();
×
624
            width = (std::max) (width, ss.str().size());
×
625
        }
626

627
        /* Get width of groups as well*/
×
628
        for (auto const& group : groups)
×
629
            width = (std::max) (width, group->get_option_column_width());
630

631
        /* this is the column were description should start, if first
632
           column is longer, we go to a new line */
×
633
        std::size_t const start_of_description_column =
×
634
            m_line_length - m_min_description_length;
635

×
636
        width = (std::min) (width, start_of_description_column - 1);
637

638
        ++width;
×
639
        return width;
×
640
    }
641

642
    void options_description::print(std::ostream& os, std::size_t width) const
×
643
    {
644
        if (!m_caption.empty())
×
645
            os << m_caption << ":\n";
×
646

647
        if (!width)
×
648
            width = get_option_column_width();
×
649

650
        /* The options formatting style is stolen from Subversion. */
651
        for (std::size_t i = 0; i < m_options.size(); ++i)
×
652
        {
653
            if (belong_to_group[i])
×
654
                continue;
×
655

656
            option_description const& opt = *m_options[i];
657

658
            format_one(os, opt, width, m_line_length);
×
659

660
            os << "\n";
×
661
        }
662

663
        for (auto const& group : groups)
×
664
        {
665
            os << "\n";
×
666
            group->print(os, width);
×
667
        }
668
    }
×
669
}    // 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