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

MikkelSchubert / adapterremoval / #75

24 Mar 2025 09:26PM UTC coverage: 27.778% (+0.7%) from 27.088%
#75

push

travis-ci

web-flow
rework debug serialization (#97)

- Patch catch2 to disable the built-in stringification of objects with
  begin()/end() functions, to prevent complex objects from being mangled
- Specify custom fallbackStringifier function, that uses a StringStream.
  This ensures that support for stringification is present when tests are
  written, rather than objects being emitted as "{?}"
- Replace existing StringMaker implementations with operator<< functions
- Add tests for these functions

91 of 103 new or added lines in 8 files covered. (88.35%)

11 existing lines in 3 files now uncovered.

2687 of 9673 relevant lines covered (27.78%)

4228.2 hits per line

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

92.25
/src/argparse.cpp
1
// SPDX-License-Identifier: GPL-3.0-or-later
2
// SPDX-FileCopyrightText: 2011 Stinus Lindgreen <stinus@binf.ku.dk>
3
// SPDX-FileCopyrightText: 2014 Mikkel Schubert <mikkelsch@gmail.com>
4
#include "argparse.hpp"
5
#include "debug.hpp"    // for AR_REQUIRE
6
#include "logging.hpp"  // for log_stream, error, cerr, warn
7
#include "strutils.hpp" // for string_vec, shell_escape, to_lower
8
#include <algorithm>    // for max, copy, find, min, sort
9
#include <limits>       // for numeric_limits
10
#include <memory>       // for __shared_ptr_access, unique_ptr, share...
11
#include <sstream>      // for operator<<, basic_ostream, ostringstream
12
#include <stdexcept>    // for invalid_argument
13
#include <utility>      // for pair
14

15
namespace adapterremoval {
16

17
namespace argparse {
18

19
namespace {
20

21
const size_t parsing_failed = static_cast<size_t>(-1);
22
const size_t invalid_choice = static_cast<size_t>(-2);
23

24
/** Detect similar arguments based on prefixes or max edit distance. */
25
bool
26
is_similar_argument(const std::string& user,
31✔
27
                    const std::string& ref,
28
                    size_t max_distance)
29
{
30
  const auto overlap = std::min(user.size(), ref.size());
124✔
31
  if (overlap == user.size() && user == ref.substr(0, overlap)) {
98✔
32
    return true;
33
  }
34

35
  const auto diff = std::max(user.size(), ref.size()) - overlap;
100✔
36

37
  return (diff <= max_distance && levenshtein(user, ref) <= max_distance);
35✔
38
}
39

40
} // namespace
41

42
///////////////////////////////////////////////////////////////////////////////
43

44
parser::parser()
210✔
45
{
46
  add_header("OPTIONS:");
60✔
47

48
  // Built-in arguments
49
  add("--help").abbreviation('h').help("Display this message");
120✔
50
  add("--version").abbreviation('v').help("Print the version string");
120✔
51
  add("--licenses").help("Print licenses for this software");
120✔
52
  add_separator();
30✔
53
}
30✔
54

55
void
56
parser::set_name(const std::string& name)
13✔
57
{
58
  m_name = name;
13✔
59
}
13✔
60

61
void
62
parser::set_version(const std::string& version)
12✔
63
{
64
  m_version = version;
12✔
65
}
12✔
66

67
void
68
parser::set_preamble(const std::string& text)
6✔
69
{
70
  m_preamble = text;
6✔
71
}
6✔
72

73
void
74
parser::set_licenses(const std::string& text)
1✔
75
{
76
  m_licenses = text;
1✔
77
}
1✔
78

79
parse_result
80
parser::parse_args(const string_vec& args)
21✔
81
{
82
  AR_REQUIRE(!args.empty());
42✔
83
  update_argument_map();
21✔
84

85
  for (auto it = args.begin() + 1; it != args.end();) {
156✔
86
    const auto argument = find_argument(*it);
50✔
87
    if (argument) {
25✔
88
      const size_t consumed = argument->parse(it, args.end());
57✔
89

90
      if (consumed == parsing_failed) {
19✔
91
        return parse_result::error;
92
      }
93

94
      it += static_cast<string_vec::iterator::difference_type>(consumed);
17✔
95
      AR_REQUIRE(it <= args.end());
51✔
96
    } else {
97
      return parse_result::error;
98
    }
99
  }
25✔
100

101
  parse_result result = parse_result::ok;
13✔
102
  for (const auto& arg : m_args) {
424✔
103
    if (arg.argument && arg.argument->is_set()) {
294✔
104
      const auto& key = arg.argument->key();
51✔
105

106
      for (const auto& requirement : arg.argument->depends_on()) {
110✔
107
        if (!is_set(requirement)) {
2✔
108
          result = parse_result::error;
1✔
109
          log::error() << "Option " << requirement << " is required when "
5✔
110
                       << "using option " << key;
3✔
111
        }
112
      }
113

114
      for (const auto& prohibited : arg.argument->conflicts_with()) {
110✔
115
        if (is_set(prohibited)) {
2✔
116
          result = parse_result::error;
1✔
117
          log::error() << "Option " << prohibited
4✔
118
                       << " cannot be used together with option " << key;
3✔
119
        }
120
      }
121
    }
122
  }
123

124
  if (is_set("--help")) {
26✔
125
    print_help();
4✔
126
    return parse_result::exit;
4✔
127
  } else if (is_set("--version")) {
18✔
128
    print_version();
2✔
129
    return parse_result::exit;
2✔
130
  } else if (is_set("--licenses")) {
14✔
131
    print_licenses();
1✔
132
    return parse_result::exit;
1✔
133
  }
134

135
  return result;
136
}
137

138
bool
139
parser::is_set(const std::string& key) const
36✔
140
{
141
  const auto it = m_keys.find(key);
36✔
142
  AR_REQUIRE(it != m_keys.end(), shell_escape(key));
108✔
143

144
  return it->second->is_set();
144✔
145
}
146

147
std::string
148
parser::value(const std::string& key) const
2✔
149
{
150
  const auto it = m_keys.find(key);
2✔
151
  AR_REQUIRE(it != m_keys.end(), shell_escape(key));
6✔
152

153
  return it->second->value();
8✔
154
}
155

156
argument&
157
parser::add(const std::string& name, const std::string& metavar)
122✔
158
{
159
  AR_REQUIRE(starts_with(name, "--"), name);
366✔
160
  auto ptr = std::make_shared<argument>(name, metavar);
122✔
161
  m_args.push_back({ std::string(), ptr });
488✔
162

163
  return *ptr;
244✔
164
}
122✔
165

166
void
167
parser::add_separator()
60✔
168
{
169
  m_args.push_back({ std::string(), argument_ptr() });
180✔
170
}
60✔
171

172
void
173
parser::add_header(const std::string& header)
30✔
174
{
175
  add_separator();
30✔
176
  m_args.push_back({ header, argument_ptr() });
60✔
177
}
30✔
178

179
void
180
parser::print_version() const
14✔
181
{
182
  log::cerr() << m_name << " " << m_version << "\n";
84✔
183
}
14✔
184

185
void
186
parser::print_help() const
12✔
187
{
188
  print_version();
12✔
189

190
  auto cerr = log::cerr();
12✔
191
  if (!m_preamble.empty()) {
24✔
192
    cli_formatter fmt;
6✔
193
    fmt.set_indent(0);
6✔
194
    fmt.set_indent_first_line(false);
6✔
195
    fmt.set_column_width(m_terminal_width);
6✔
196

197
    cerr << "\n";
6✔
198
    for (auto line : split_lines(m_preamble)) {
54✔
199
      if (starts_with(line, " ")) {
18✔
200
        cerr << line << "\n";
6✔
201
      } else {
202
        cerr << fmt.format(line) << "\n";
30✔
203
      }
204
    }
12✔
205
  }
206

207
  string_vec signatures;
12✔
208

209
  size_t indentation = 0;
12✔
210
  for (const auto& entry : m_args) {
376✔
211
    if (entry.argument && !entry.argument->is_hidden()) {
256✔
212
      const auto& arg = *entry.argument;
44✔
213

214
      std::ostringstream ss;
44✔
215
      if (arg.short_key().empty()) {
132✔
216
        ss << "   " << arg.key();
40✔
217
      } else {
218
        ss << "   " << arg.short_key() << ", " << arg.key();
96✔
219
      }
220

221
      for (size_t i = 0; i < arg.min_values(); ++i) {
50✔
222
        ss << " <" << arg.metavar() << ">";
18✔
223
      }
224

225
      if (arg.max_values() == std::numeric_limits<size_t>::max()) {
44✔
226
        ss << " [" << arg.metavar() << ", ...]";
6✔
227
      } else {
228
        for (size_t i = arg.min_values(); i < arg.max_values(); ++i) {
45✔
229
          ss << " [" << arg.metavar() << "]";
9✔
230
        }
231
      }
232

233
      indentation = std::max<size_t>(indentation, ss.str().size() + 3);
220✔
234
      signatures.emplace_back(ss.str());
132✔
235
    } else {
44✔
236
      signatures.emplace_back();
38✔
237
    }
238
  }
239

240
  cli_formatter fmt;
12✔
241
  fmt.set_indent(indentation);
12✔
242
  fmt.set_indent_first_line(false);
12✔
243
  fmt.set_column_width(m_terminal_width - indentation);
12✔
244

245
  for (size_t i = 0; i < m_args.size(); i++) {
188✔
246
    const auto& entry = m_args.at(i);
82✔
247
    if (entry.argument) {
164✔
248
      if (!entry.argument->is_hidden()) {
138✔
249
        const auto& arg = *entry.argument;
44✔
250
        const auto& signature = signatures.at(i);
44✔
251

252
        cerr << signature;
44✔
253

254
        const std::string help = arg.help();
44✔
255
        if (!help.empty()) {
88✔
256
          // Format into columns and indent lines (except the first line)
257
          cerr << std::string(indentation - signature.length(), ' ')
168✔
258
               << fmt.format(help);
168✔
259
        }
260

261
        cerr << "\n";
88✔
262
      }
44✔
263
    } else {
264
      cerr << entry.header << "\n";
154✔
265
    }
266
  }
267
}
24✔
268

269
void
270
parser::print_licenses() const
1✔
271
{
272
  auto cerr = log::cerr();
1✔
273

274
  for (const auto& block : split_lines(m_licenses)) {
15✔
275
    if (block.empty()) {
6✔
276
      cerr << "\n";
3✔
277
    } else {
278
      size_t indentation = block.find_first_not_of(' ');
3✔
279
      if (indentation == std::string::npos) {
3✔
280
        indentation = 0;
×
281
      }
282

283
      size_t ljust = 0;
3✔
284
      if (!block.empty() && block.at(indentation) == '*') {
6✔
285
        ljust = 2;
286
      } else if (block.size() > indentation + 1 &&
6✔
287
                 block.at(indentation + 1) == '.') {
3✔
288
        ljust = 3;
289
      }
290

291
      const auto width = 80 - indentation;
3✔
292
      for (const auto& line : wrap_text(block, width, ljust)) {
24✔
293
        cerr << std::string(indentation, ' ') << line << "\n";
18✔
294
      }
3✔
295
    }
296
  }
1✔
297
}
2✔
298

299
void
300
parser::set_terminal_width(unsigned w)
6✔
301
{
302
  m_terminal_width = w;
6✔
303
}
6✔
304

305
void
306
parser::update_argument_map()
21✔
307
{
308
  m_keys.clear();
21✔
309

310
  for (auto& it : m_args) {
680✔
311
    if (it.argument) {
298✔
312
      for (const auto& key : it.argument->keys()) {
814✔
313
        const auto result = m_keys.emplace(key, it.argument);
128✔
314
        AR_REQUIRE(result.second, shell_escape(key));
128✔
315
      }
86✔
316
    }
317
  }
318

319
  bool any_errors = false;
21✔
320
  for (const auto& it : m_args) {
680✔
321
    if (it.argument) {
298✔
322
      for (const auto& key : it.argument->conflicts_with()) {
524✔
323
        if (!m_keys.count(key)) {
2✔
324
          any_errors = true;
×
325
          log::error() << it.argument->key() << " conflicts with "
×
326
                       << "unknown command-line option " << key;
×
327
        }
328
      }
329

330
      for (const auto& key : it.argument->depends_on()) {
524✔
331
        if (!m_keys.count(key)) {
2✔
332
          any_errors = true;
×
333
          log::error() << it.argument->key() << " requires "
×
334
                       << "unknown command-line option " << key;
×
335
        }
336
      }
337
    }
338
  }
339

340
  AR_REQUIRE(!any_errors, "bugs in argument parsing");
21✔
341
}
21✔
342

343
argument_ptr
344
parser::find_argument(const std::string& key)
25✔
345
{
346
  auto it = m_keys.find(key);
25✔
347
  if (it != m_keys.end()) {
75✔
348
    return it->second;
38✔
349
  }
350

351
  if (key.size() > 1 && key.front() == '-' && key.back() != '-') {
12✔
352
    string_vec candidates;
5✔
353
    const size_t max_distance = 1 + key.size() / 4;
5✔
354

355
    for (const auto& arg : m_args) {
164✔
356
      if (arg.argument) {
72✔
357
        for (const auto& name : arg.argument->keys()) {
229✔
358

359
          if (is_similar_argument(key, name, max_distance)) {
31✔
360
            candidates.push_back(name);
7✔
361
          }
362
        }
21✔
363
      }
364
    }
365

366
    auto error = log::error();
5✔
367
    error << "Unknown argument '" << key << "'";
15✔
368

369
    if (!candidates.empty()) {
10✔
370
      std::sort(candidates.begin(), candidates.end());
12✔
371

372
      error << ". Did you mean " << candidates.front();
8✔
373
      for (size_t i = 1; i < candidates.size() - 1; ++i) {
10✔
374
        error << ", " << candidates.at(i);
3✔
375
      }
376

377
      if (candidates.size() > 1) {
8✔
378
        error << " or " << candidates.back();
4✔
379
      }
380

381
      error << "?";
4✔
382
    }
383
  } else {
5✔
384
    log::error() << "Unexpected positional argument '" << key << "'";
5✔
385
  }
386

387
  return {};
31✔
388
}
389

390
///////////////////////////////////////////////////////////////////////////////
391

392
argument::argument(const std::string& key, std::string metavar)
148✔
393
  : m_key_long(key)
148✔
394
  , m_metavar(std::move(metavar))
148✔
395
  , m_sink(std::make_unique<bool_sink>(&m_default_sink))
1,332✔
396
{
397
  AR_REQUIRE(!key.empty() && key.at(0) == '-', shell_escape(key));
296✔
398
}
148✔
399

400
argument&
401
argument::help(const std::string& text)
103✔
402
{
403
  // Enforce this the style of help text
404
  AR_REQUIRE(!ends_with(text, "."),
318✔
405
             "Help text ends with dot: " + log_escape(text));
406
  m_help = text;
102✔
407

408
  return *this;
102✔
409
}
410

411
string_vec
412
argument::keys() const
110✔
413
{
414
  string_vec keys = m_deprecated_keys;
110✔
415
  keys.push_back(m_key_long);
110✔
416

417
  if (!m_key_short.empty()) {
220✔
418
    keys.push_back(m_key_short);
53✔
419
  }
420

421
  return keys;
110✔
422
}
×
423

424
std::string
425
argument::help() const
51✔
426
{
427
  // Append string representation of current (default) value
428
  std::ostringstream ss(m_help);
51✔
429
  ss << m_help;
51✔
430

431
  const auto choices = m_sink->choices();
102✔
432
  if (!choices.empty()) {
102✔
433
    ss << ". Possible values are " << join_text(choices, ", ", ", and ");
15✔
434
  }
435

436
  if (m_sink->has_default() && m_help.find("[default:") == std::string::npos) {
102✔
437
    ss << " [default: " << default_value() << "]";
6✔
438
  }
439

440
  return ss.str();
102✔
441
}
51✔
442

443
size_t
444
argument::min_values() const
92✔
445
{
446
  return m_sink->min_values();
184✔
447
}
448

449
size_t
450
argument::max_values() const
89✔
451
{
452
  return m_sink->max_values();
178✔
453
}
454

455
std::string
456
argument::value() const
3✔
457
{
458
  return m_sink->value();
6✔
459
}
460

461
std::string
462
argument::default_value() const
2✔
463
{
464
  return m_sink->default_value();
4✔
465
}
466

467
template<typename A, typename B>
468
A&
469
bind(std::unique_ptr<sink>& ptr, B* sink)
23✔
470
{
471
  ptr = std::make_unique<A>(sink);
23✔
472

473
  return static_cast<A&>(*ptr);
23✔
474
}
475

5✔
476
bool_sink&
477
argument::bind_bool(bool* ptr)
5✔
478
{
479
  return bind<bool_sink>(m_sink, ptr);
5✔
480
}
481

5✔
482
u32_sink&
483
argument::bind_u32(unsigned* ptr)
5✔
484
{
485
  return bind<u32_sink>(m_sink, ptr);
5✔
486
}
487

2✔
488
double_sink&
489
argument::bind_double(double* ptr)
2✔
490
{
491
  return bind<double_sink>(m_sink, ptr);
2✔
492
}
493

10✔
494
str_sink&
495
argument::bind_str(std::string* ptr)
10✔
496
{
497
  return bind<str_sink>(m_sink, ptr);
10✔
498
}
499

1✔
500
vec_sink&
501
argument::bind_vec(string_vec* ptr)
1✔
502
{
503
  return bind<vec_sink>(m_sink, ptr);
1✔
504
}
505

506
argument&
507
argument::abbreviation(char key)
1✔
508
{
509
  // To avoid ambiguity only lower-case alphabetical arguments are allowed
1✔
510
  AR_REQUIRE(key >= 'a' && key <= 'z', std::string{ key });
511
  m_key_short.clear();
512
  m_key_short.push_back('-');
513
  m_key_short.push_back(key);
10✔
514

515
  return *this;
10✔
516
}
517

518
argument&
519
argument::deprecated_alias(const std::string& key)
2✔
520
{
521
  AR_REQUIRE(!key.empty() && key.at(0) == '-', shell_escape(key));
2✔
522
  m_deprecated_keys.emplace_back(key);
523

524
  return *this;
525
}
5✔
526

527
argument&
5✔
528
argument::deprecated()
529
{
530
  m_deprecated = true;
531

5✔
532
  return hidden();
533
}
5✔
534

535
argument&
536
argument::hidden()
537
{
61✔
538
  m_hidden = true;
539

540
  return *this;
61✔
541
}
61✔
542

61✔
543
argument&
61✔
544
argument::depends_on(const std::string& key)
545
{
61✔
546
  AR_REQUIRE(!key.empty() && key.at(0) == '-', shell_escape(key));
547
  m_depends_on.push_back(key);
548

549
  return *this;
3✔
550
}
551

6✔
552
argument&
3✔
553
argument::conflicts_with(const std::string& key)
554
{
3✔
555
  AR_REQUIRE(!key.empty() && key.at(0) == '-', shell_escape(key));
556
  m_conflicts_with.push_back(key);
557

558
  return *this;
2✔
559
}
560

2✔
561
void
562
n_args_error(const std::string& key,
2✔
563
             size_t limit,
564
             const char* relation,
565
             size_t n)
566
{
4✔
567
  auto out = log::error();
568

4✔
569
  out << "Command-line argument " << key << " takes" << relation << " " << limit
570
      << " value";
4✔
571

572
  if (limit != 1) {
573
    out << "s";
574
  }
4✔
575

576
  if (n == 1) {
8✔
577
    out << ", but 1 value was provided!";
4✔
578
  } else {
579
    out << ", but " << n << " values were provided!";
4✔
580
  }
581
}
582

583
size_t
4✔
584
argument::parse(string_vec_citer start, const string_vec_citer& end)
585
{
8✔
586
  AR_REQUIRE(start != end);
4✔
587

588
  const bool deprecated_alias = is_deprecated_alias(*start);
4✔
589
  AR_REQUIRE(deprecated_alias || *start == m_key_long ||
590
             (!m_key_short.empty() && *start == m_key_short));
591

592
  if (m_deprecated) {
1✔
593
    log::warn() << "Option " << *start << " is deprecated and will "
594
                << "be removed in the future.";
595
  } else if (deprecated_alias) {
596
    log::warn() << "Option " << *start << " has been renamed to " << key()
597
                << ". Support for the old name will be removed in the future.";
1✔
598
  }
599

6✔
600
  if (m_times_set == 1) {
1✔
601
    log::warn() << "Command-line option " << key()
602
                << " has been specified more than once.";
1✔
603
  }
×
604

605
  auto end_of_values = start + 1;
606
  for (; end_of_values != end; ++end_of_values) {
1✔
607
    if (end_of_values->size() > 1 && end_of_values->front() == '-') {
×
608
      // Avoid confusing numeric values for command-line arguments
609
      try {
3✔
610
        str_to_double(*end_of_values);
611
      } catch (const std::invalid_argument&) {
2✔
612
        break;
613
      }
614
    }
33✔
615
  }
616

66✔
617
  const auto min_values = m_sink->min_values();
618
  const auto max_values = m_sink->max_values();
66✔
619

77✔
620
  AR_REQUIRE(start < end_of_values);
621
  auto n_values = static_cast<size_t>(end_of_values - start - 1);
622

32✔
623
  if (n_values != min_values && min_values == max_values) {
6✔
624
    n_args_error(*start, min_values, "", n_values);
2✔
625

31✔
626
    return parsing_failed;
8✔
627
  } else if (n_values < min_values) {
2✔
628
    n_args_error(*start, min_values, " at least", n_values);
629

630
    return parsing_failed;
32✔
631
  } else if (n_values > max_values) {
5✔
632
    n_args_error(*start, max_values, " at most", n_values);
2✔
633

634
    return parsing_failed;
635
  }
32✔
636

94✔
637
  size_t result = parsing_failed;
42✔
638
  std::string error_message;
639

5✔
640
  try {
10✔
641
    result = m_sink->consume(start + 1, end_of_values);
4✔
642
  } catch (const std::invalid_argument& error) {
4✔
643
    error_message = error.what();
4✔
644
  }
645

646
  if (result == parsing_failed) {
647
    auto error = log::error();
64✔
648

64✔
649
    error << "Invalid command-line argument " << *start;
650
    for (auto it = start + 1; it != end_of_values; ++it) {
64✔
651
      error << " " << shell_escape(*it);
32✔
652
    }
653

32✔
654
    if (!error_message.empty()) {
2✔
655
      error << ": " << error_message;
656
    }
657

31✔
658
    return result;
×
659
  } else if (result == invalid_choice) {
660
    auto error = log::error();
661

31✔
662
    error << "Invalid command-line argument " << *start;
×
663
    for (auto it = start + 1; it != end_of_values; ++it) {
664
      error << " " << shell_escape(*it);
665
    }
666

667
    error << ". Valid values for " << *start << " are "
31✔
668
          << join_text(m_sink->choices(), ", ", ", and ");
31✔
669
  }
670

31✔
671
  m_times_set++;
93✔
672
  return result + 1;
1✔
673
}
1✔
674

×
675
bool
676
argument::is_deprecated_alias(const std::string& key) const
31✔
677
{
1✔
678
  return std::find(m_deprecated_keys.begin(), m_deprecated_keys.end(), key) !=
679
         m_deprecated_keys.end();
3✔
680
}
6✔
681

6✔
682
///////////////////////////////////////////////////////////////////////////////
683
// sink
684

2✔
685
sink::sink(size_t n_values)
2✔
686
  : sink(n_values, n_values)
687
{
688
}
1✔
689

31✔
690
sink::sink(size_t min_values, size_t max_values)
×
691
  : m_min_values(min_values)
692
  , m_max_values(max_values)
×
693
{
×
694
}
×
695

696
std::string
697
sink::default_value() const
×
698
{
×
699
  AR_FAIL("sink::default_value not implemented");
700
}
701

30✔
702
std::string
30✔
703
sink::preprocess(std::string value) const
32✔
704
{
705
  if (m_preprocess) {
706
    m_preprocess(value);
38✔
707
  }
708

152✔
709
  return value;
114✔
710
}
711

712
///////////////////////////////////////////////////////////////////////////////
713
// bool_sink
714

715
bool_sink::bool_sink(bool* ptr)
223✔
716
  : sink(0)
223✔
717
  , m_sink(ptr ? ptr : &m_fallback_sink)
718
{
223✔
719
  *m_sink = false;
720
}
239✔
721

239✔
722
std::string
478✔
723
bool_sink::value() const
724
{
239✔
725
  return *m_sink ? "on" : "off";
726
}
727

×
728
size_t
729
bool_sink::consume(string_vec_citer start, const string_vec_citer& end)
×
730
{
731
  AR_REQUIRE(start == end);
732
  *m_sink = true;
733

35✔
734
  return 0;
735
}
35✔
736

×
737
///////////////////////////////////////////////////////////////////////////////
738
// u32_sink
739

35✔
740
u32_sink::u32_sink(uint32_t* ptr)
741
  : sink(1)
742
  , m_sink(ptr)
743
  , m_minimum(std::numeric_limits<decltype(m_minimum)>::lowest())
744
  , m_maximum(std::numeric_limits<decltype(m_maximum)>::max())
745
{
154✔
746
  AR_REQUIRE(ptr);
747

154✔
748
  *m_sink = m_default;
749
}
154✔
750

154✔
751
u32_sink&
752
u32_sink::with_default(uint32_t value)
753
{
4✔
754
  AR_REQUIRE(value >= m_minimum && value <= m_maximum);
755
  set_has_default();
10✔
756
  *m_sink = m_default = value;
757

758
  return *this;
759
}
24✔
760

761
std::string
55✔
762
u32_sink::value() const
23✔
763
{
764
  return std::to_string(*m_sink);
23✔
765
}
766

767
std::string
768
u32_sink::default_value() const
769
{
770
  AR_REQUIRE(has_default());
35✔
771
  return std::to_string(m_default);
772
}
35✔
773

35✔
774
u32_sink&
105✔
775
u32_sink::with_minimum(uint32_t value)
776
{
42✔
777
  AR_REQUIRE(m_default >= value && value <= m_maximum);
778
  m_minimum = value;
34✔
779
  return *this;
35✔
780
}
781

782
u32_sink&
13✔
783
u32_sink::with_maximum(uint32_t value)
784
{
13✔
785
  AR_REQUIRE(m_default <= value && value >= m_minimum);
13✔
786
  m_maximum = value;
13✔
787
  return *this;
788
}
13✔
789

790
size_t
791
u32_sink::consume(string_vec_citer start, const string_vec_citer& end)
792
{
3✔
793
  AR_REQUIRE(end - start == 1);
794

3✔
795
  const auto value = str_to_u32(preprocess(*start));
796
  if (value < m_minimum) {
797
    throw std::invalid_argument("value must be at least " +
798
                                std::to_string(m_minimum));
2✔
799
  } else if (value > m_maximum) {
800
    throw std::invalid_argument("value must be at most " +
2✔
801
                                std::to_string(m_maximum));
2✔
802
  }
803

804
  *m_sink = value;
805
  return 1;
8✔
806
}
807

15✔
808
///////////////////////////////////////////////////////////////////////////////
7✔
809
// double_sink
7✔
810

811
double_sink::double_sink(double* ptr)
812
  : sink(1)
813
  , m_sink(ptr)
9✔
814
  , m_minimum(std::numeric_limits<decltype(m_minimum)>::lowest())
815
  , m_maximum(std::numeric_limits<decltype(m_maximum)>::max())
16✔
816
{
8✔
817
  AR_REQUIRE(ptr);
8✔
818
  *m_sink = m_default;
819
}
820

821
double_sink&
21✔
822
double_sink::with_default(double value)
823
{
56✔
824
  AR_REQUIRE(value >= m_minimum && value <= m_maximum);
825
  set_has_default();
95✔
826
  *m_sink = m_default = value;
14✔
827

3✔
828
  return *this;
3✔
829
}
13✔
830

3✔
831
namespace {
3✔
832

833
std::string
834
double_to_string(double value)
12✔
835
{
12✔
836
  auto s = std::to_string(value);
837
  s.erase(s.find_last_not_of('0') + 1, std::string::npos);
838
  s.erase(s.find_last_not_of('.') + 1, std::string::npos);
839

840
  return s;
841
}
12✔
842

843
} // namespace
12✔
844

12✔
845
std::string
36✔
846
double_sink::default_value() const
847
{
19✔
848
  AR_REQUIRE(has_default());
11✔
849
  return double_to_string(m_default);
12✔
850
}
851

852
double_sink&
2✔
853
double_sink::with_minimum(double value)
854
{
2✔
855
  AR_REQUIRE(m_default >= value && value >= m_minimum);
2✔
856
  m_minimum = value;
2✔
857

858
  return *this;
2✔
859
}
860

861
double_sink&
862
double_sink::with_maximum(double value)
863
{
864
  AR_REQUIRE(m_default <= value && value >= m_minimum);
3✔
865
  m_maximum = value;
866
  return *this;
3✔
867
}
3✔
868

3✔
869
std::string
870
double_sink::value() const
3✔
871
{
×
872
  return double_to_string(*m_sink);
873
}
874

875
size_t
876
double_sink::consume(string_vec_citer start, const string_vec_citer& end)
×
877
{
878
  AR_REQUIRE(end - start == 1);
×
879

×
880
  const auto value = str_to_double(*start);
881
  if (value < m_minimum) {
882
    throw std::invalid_argument("value must be at least " +
883
                                double_to_string(m_minimum));
×
884
  } else if (value > m_maximum) {
885
    throw std::invalid_argument("value must be at most " +
×
886
                                double_to_string(m_maximum));
×
887
  }
888

×
889
  *m_sink = value;
890
  return 1;
891
}
892

×
893
///////////////////////////////////////////////////////////////////////////////
894
// str_sink
×
895

×
896
str_sink::str_sink(std::string* ptr)
×
897
  : sink(1)
898
  , m_sink(ptr ? ptr : &m_fallback_sink)
899
{
900
  AR_REQUIRE(m_sink);
3✔
901

902
  *m_sink = m_default;
3✔
903
}
904

905
str_sink&
906
str_sink::with_default(std::string_view value)
7✔
907
{
908
  set_has_default();
28✔
909
  *m_sink = m_default = value;
910

10✔
911
  return *this;
3✔
912
}
×
913

×
914
str_sink&
3✔
915
str_sink::with_choices(const string_vec& choices)
×
916
{
×
917
  m_choices = choices;
918

919
  return *this;
3✔
920
}
3✔
921

922
str_sink&
923
str_sink::with_implicit_argument(std::string_view value)
924
{
925
  m_has_implicit_argument = true;
926
  m_implicit_argument = value;
22✔
927
  set_min_values(0);
928

88✔
929
  return *this;
930
}
22✔
931

932
std::string
22✔
933
str_sink::value() const
22✔
934
{
935
  return *m_sink;
936
}
5✔
937

938
std::string
5✔
939
str_sink::default_value() const
10✔
940
{
941
  AR_REQUIRE(has_default());
5✔
942
  return m_default;
943
}
944

945
size_t
8✔
946
str_sink::consume(string_vec_citer start, const string_vec_citer& end)
947
{
8✔
948
  AR_REQUIRE(end - start <= 1);
949

8✔
950
  size_t consumed = 0;
951
  std::string argument;
952
  if (start == end) {
953
    AR_REQUIRE(m_has_implicit_argument);
1✔
954
    argument = m_implicit_argument;
955
    consumed = 0;
1✔
956
  } else {
1✔
957
    argument = *start;
1✔
958
    consumed = 1;
959
  }
1✔
960

961
  if (m_choices.empty()) {
962
    *m_sink = preprocess(argument);
963
    return consumed;
3✔
964
  } else {
965
    const auto value = preprocess(argument);
3✔
966
    const auto choice = to_lower(value);
967
    for (const auto& it : m_choices) {
968
      if (choice == to_lower(it)) {
969
        *m_sink = it;
×
970
        return consumed;
971
      }
×
972
    }
×
973

974
    return invalid_choice;
975
  }
976
}
12✔
977

978
vec_sink::vec_sink(string_vec* ptr)
31✔
979
  : sink(1, std::numeric_limits<size_t>::max())
980
  , m_sink(ptr)
11✔
981
{
11✔
982
  AR_REQUIRE(ptr);
22✔
983

9✔
984
  m_sink->clear();
1✔
985
}
1✔
986

987
vec_sink&
18✔
988
vec_sink::with_min_values(size_t n)
9✔
989
{
990
  AR_REQUIRE(n <= max_values());
991
  set_min_values(n);
20✔
992

14✔
993
  return *this;
7✔
994
}
995

6✔
996
vec_sink&
6✔
997
vec_sink::with_max_values(size_t n)
42✔
998
{
27✔
999
  AR_REQUIRE(n >= min_values());
2✔
1000
  set_max_values(n);
2✔
1001

1002
  return *this;
1003
}
1004

1✔
1005
size_t
6✔
1006
vec_sink::consume(string_vec_citer start, const string_vec_citer& end)
11✔
1007
{
1008
  AR_REQUIRE(static_cast<size_t>(end - start) >= min_values());
16✔
1009
  AR_REQUIRE(static_cast<size_t>(end - start) <= max_values());
1010
  for (auto it = start; it != end; ++it) {
16✔
1011
    m_sink->emplace_back(preprocess(*it));
1012
  }
23✔
1013

1014
  return static_cast<size_t>(end - start);
15✔
1015
}
16✔
1016

1017
std::string
1018
vec_sink::value() const
5✔
1019
{
1020
  std::string output;
10✔
1021

5✔
1022
  for (const auto& s : *m_sink) {
1023
    if (!output.empty()) {
5✔
1024
      output.push_back(';');
1025
    }
1026

1027
    output.append(shell_escape(s));
3✔
1028
  }
1029

6✔
1030
  return output;
3✔
1031
}
1032

3✔
1033
std::ostream&
1034
operator<<(std::ostream& os, const parse_result& value)
1035
{
1036
  switch (value) {
5✔
1037
    case parse_result::exit:
1038
      return os << "parse_result::exit";
22✔
1039
    case parse_result::error:
12✔
1040
      return os << "parse_result::error";
26✔
1041
    case parse_result::ok:
24✔
1042
      return os << "parse_result::ok";
1043
    default:
1044
      AR_FAIL("invalid parse_result");
8✔
1045
  }
1046
}
1047

1048
} // namespace argparse
3✔
1049

1050
} // namespace adapterremoval
3✔
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

© 2026 Coveralls, Inc