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

MikkelSchubert / adapterremoval / #38

07 Aug 2024 07:47PM UTC coverage: 83.365% (-3.5%) from 86.839%
#38

push

travis-ci

MikkelSchubert
additional tests

2190 of 2627 relevant lines covered (83.37%)

15528.72 hits per line

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

93.58
/src/argparse.cpp
1
/*************************************************************************\
2
 * AdapterRemoval - cleaning next-generation sequencing reads            *
3
 *                                                                       *
4
 * Copyright (C) 2011 by Stinus Lindgreen - stinus@binf.ku.dk            *
5
 * Copyright (C) 2014 by Mikkel Schubert - mikkelsch@gmail.com           *
6
 *                                                                       *
7
 * This program is free software: you can redistribute it and/or modify  *
8
 * it under the terms of the GNU General Public License as published by  *
9
 * the Free Software Foundation, either version 3 of the License, or     *
10
 * (at your option) any later version.                                   *
11
 *                                                                       *
12
 * This program is distributed in the hope that it will be useful,       *
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
15
 * GNU General Public License for more details.                          *
16
 *                                                                       *
17
 * You should have received a copy of the GNU General Public License     *
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. *
19
\*************************************************************************/
20
#include "argparse.hpp"
21
#include "debug.hpp"    // for AR_REQUIRE
22
#include "logging.hpp"  // for log_stream, error, cerr, warn
23
#include "strutils.hpp" // for string_vec, shell_escape, to_lower
24
#include <algorithm>    // for max, copy, find, min, sort
25
#include <limits>       // for numeric_limits
26
#include <memory>       // for __shared_ptr_access, unique_ptr, share...
27
#include <sstream>      // for operator<<, basic_ostream, ostringstream
28
#include <stdexcept>    // for invalid_argument
29
#include <utility>      // for pair
30

31
namespace adapterremoval {
32

33
namespace argparse {
34

35
namespace {
36

37
const size_t parsing_failed = static_cast<size_t>(-1);
38
const size_t invalid_choice = static_cast<size_t>(-2);
39

40
/** Detect similar arguments based on prefixes or max edit distance. */
41
bool
42
is_similar_argument(const std::string& user,
31✔
43
                    const std::string& ref,
44
                    size_t max_distance)
45
{
46
  const auto overlap = std::min(user.size(), ref.size());
124✔
47
  if (overlap == user.size() && user == ref.substr(0, overlap)) {
104✔
48
    return true;
49
  }
50

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

53
  return (diff <= max_distance && levenshtein(user, ref) <= max_distance);
35✔
54
}
55

56
bool
57
to_double(const std::string& value, double& out)
5✔
58
{
59
  std::istringstream stream(value);
5✔
60
  if (!(stream >> out)) {
15✔
61
    return false;
62
  }
63

64
  // Failing on trailing, non-numerical values
65
  char trailing = 0;
1✔
66
  return (!(stream >> trailing));
2✔
67
}
5✔
68

69
} // namespace
70

71
///////////////////////////////////////////////////////////////////////////////
72

73
parser::parser()
203✔
74
{
75
  add_header("OPTIONS:");
58✔
76

77
  // Built-in arguments
78
  add("--help").abbreviation('h').help("Display this message.");
145✔
79
  add("--version").abbreviation('v').help("Print the version string.");
232✔
80
  add("--licenses").help("Print licenses for this software.");
145✔
81
  add_separator();
29✔
82
}
29✔
83

84
void
85
parser::set_name(const std::string& name)
13✔
86
{
87
  m_name = name;
13✔
88
}
13✔
89

90
void
91
parser::set_version(const std::string& version)
12✔
92
{
93
  m_version = version;
12✔
94
}
12✔
95

96
void
97
parser::set_preamble(const std::string& text)
6✔
98
{
99
  m_preamble = text;
6✔
100
}
6✔
101

102
void
103
parser::set_licenses(const std::string& text)
1✔
104
{
105
  m_licenses = text;
1✔
106
}
1✔
107

108
parse_result
109
parser::parse_args(const string_vec& args)
21✔
110
{
111
  AR_REQUIRE(!args.empty());
42✔
112
  update_argument_map();
21✔
113

114
  for (auto it = args.begin() + 1; it != args.end();) {
156✔
115
    const auto argument = find_argument(*it);
50✔
116
    if (argument) {
25✔
117
      const size_t consumed = argument->parse(it, args.end());
57✔
118

119
      if (consumed == parsing_failed) {
19✔
120
        return parse_result::error;
121
      }
122

123
      it += static_cast<string_vec::iterator::difference_type>(consumed);
17✔
124
      AR_REQUIRE(it <= args.end());
51✔
125
    } else {
126
      return parse_result::error;
127
    }
128
  }
25✔
129

130
  parse_result result = parse_result::ok;
13✔
131
  for (const auto& arg : m_args) {
424✔
132
    if (arg.argument && arg.argument->is_set()) {
294✔
133
      const auto& key = arg.argument->key();
51✔
134

135
      for (const auto& requirement : arg.argument->depends_on()) {
110✔
136
        if (!is_set(requirement)) {
2✔
137
          result = parse_result::error;
1✔
138
          log::error() << "Option " << requirement << " is required when "
5✔
139
                       << "using option " << key;
3✔
140
        }
141
      }
142

143
      for (const auto& prohibited : arg.argument->conflicts_with()) {
110✔
144
        if (is_set(prohibited)) {
2✔
145
          result = parse_result::error;
1✔
146
          log::error() << "Option " << prohibited
4✔
147
                       << " cannot be used together with option " << key;
3✔
148
        }
149
      }
150
    }
151
  }
152

153
  if (is_set("--help")) {
26✔
154
    print_help();
4✔
155
    return parse_result::exit;
4✔
156
  } else if (is_set("--version")) {
18✔
157
    print_version();
2✔
158
    return parse_result::exit;
2✔
159
  } else if (is_set("--licenses")) {
14✔
160
    print_licenses();
1✔
161
    return parse_result::exit;
1✔
162
  }
163

164
  return result;
165
}
166

167
bool
168
parser::is_set(const std::string& key) const
36✔
169
{
170
  const auto it = m_keys.find(key);
36✔
171
  AR_REQUIRE(it != m_keys.end(), shell_escape(key));
108✔
172

173
  return it->second->is_set();
144✔
174
}
175

176
std::string
177
parser::value(const std::string& key) const
2✔
178
{
179
  const auto it = m_keys.find(key);
2✔
180
  AR_REQUIRE(it != m_keys.end(), shell_escape(key));
6✔
181

182
  return it->second->value();
8✔
183
}
184

185
argument&
186
parser::add(const std::string& name, const std::string& metavar)
118✔
187
{
188
  auto ptr = std::make_shared<argument>(name, metavar);
118✔
189
  m_args.push_back({ std::string(), ptr });
472✔
190

191
  return *ptr;
354✔
192
}
118✔
193

194
void
195
parser::add_separator()
58✔
196
{
197
  m_args.push_back({ std::string(), argument_ptr() });
174✔
198
}
58✔
199

200
void
201
parser::add_header(const std::string& header)
29✔
202
{
203
  add_separator();
29✔
204
  m_args.push_back({ header, argument_ptr() });
58✔
205
}
29✔
206

207
void
208
parser::print_version() const
14✔
209
{
210
  log::cerr() << m_name << " " << m_version << "\n";
84✔
211
}
14✔
212

213
void
214
parser::print_help() const
12✔
215
{
216
  print_version();
12✔
217

218
  auto cerr = log::cerr();
12✔
219
  if (!m_preamble.empty()) {
24✔
220
    cerr << "\n" << m_preamble;
12✔
221
  }
222

223
  string_vec signatures;
12✔
224

225
  size_t indentation = 0;
12✔
226
  for (const auto& entry : m_args) {
376✔
227
    if (entry.argument && !entry.argument->is_hidden()) {
256✔
228
      const auto& arg = *entry.argument;
88✔
229

230
      std::ostringstream ss;
44✔
231
      if (arg.short_key().empty()) {
132✔
232
        ss << "   " << arg.key();
40✔
233
      } else {
234
        ss << "   " << arg.short_key() << ", " << arg.key();
96✔
235
      }
236

237
      for (size_t i = 0; i < arg.min_values(); ++i) {
50✔
238
        ss << " <" << arg.metavar() << ">";
18✔
239
      }
240

241
      if (arg.max_values() == std::numeric_limits<size_t>::max()) {
44✔
242
        ss << " [" << arg.metavar() << ", ...]";
6✔
243
      } else {
244
        for (size_t i = arg.min_values(); i < arg.max_values(); ++i) {
87✔
245
          ss << " [" << arg.metavar() << "]";
9✔
246
        }
247
      }
248

249
      indentation = std::max<size_t>(indentation, ss.str().size() + 3);
220✔
250
      signatures.emplace_back(ss.str());
132✔
251
    } else {
44✔
252
      signatures.emplace_back();
38✔
253
    }
254
  }
255

256
  cli_formatter fmt;
12✔
257
  fmt.set_indent(indentation);
12✔
258
  fmt.set_indent_first_line(false);
12✔
259
  fmt.set_column_width(m_terminal_width - indentation);
12✔
260

261
  for (size_t i = 0; i < m_args.size(); i++) {
188✔
262
    const auto& entry = m_args.at(i);
164✔
263
    if (entry.argument) {
164✔
264
      if (!entry.argument->is_hidden()) {
138✔
265
        const auto& arg = *entry.argument;
88✔
266
        const auto& signature = signatures.at(i);
88✔
267

268
        cerr << signature;
44✔
269

270
        const std::string help = arg.help();
44✔
271
        if (!help.empty()) {
88✔
272
          // Format into columns and indent lines (except the first line)
273
          cerr << std::string(indentation - signature.length(), ' ')
168✔
274
               << fmt.format(help);
168✔
275
        }
276

277
        cerr << "\n";
88✔
278
      }
44✔
279
    } else {
280
      cerr << entry.header << "\n";
154✔
281
    }
282
  }
283
}
24✔
284

285
void
286
parser::print_licenses() const
1✔
287
{
288
  auto cerr = log::cerr();
1✔
289

290
  for (const auto& block : split_lines(m_licenses)) {
14✔
291
    if (block.empty()) {
6✔
292
      cerr << "\n";
3✔
293
    } else {
294
      size_t indentation = block.find_first_not_of(' ');
3✔
295
      if (indentation == std::string::npos) {
3✔
296
        indentation = 0;
×
297
      }
298

299
      size_t ljust = 0;
3✔
300
      if (!block.empty() && block.at(indentation) == '*') {
9✔
301
        ljust = 2;
302
      } else if (block.size() > indentation + 1 &&
6✔
303
                 block.at(indentation + 1) == '.') {
6✔
304
        ljust = 3;
305
      }
306

307
      const auto width = 80 - indentation;
3✔
308
      for (const auto& line : wrap_text(block, width, ljust)) {
24✔
309
        cerr << std::string(indentation, ' ') << line << "\n";
18✔
310
      }
3✔
311
    }
312
  }
1✔
313
}
2✔
314

315
void
316
parser::set_terminal_width(unsigned w)
6✔
317
{
318
  m_terminal_width = w;
6✔
319
}
6✔
320

321
void
322
parser::update_argument_map()
21✔
323
{
324
  m_keys.clear();
21✔
325

326
  for (auto& it : m_args) {
680✔
327
    if (it.argument) {
298✔
328
      for (const auto& key : it.argument->keys()) {
814✔
329
        const auto result = m_keys.emplace(key, it.argument);
128✔
330
        AR_REQUIRE(result.second, shell_escape(key));
128✔
331
      }
86✔
332
    }
333
  }
334

335
  bool any_errors = false;
21✔
336
  for (const auto& it : m_args) {
680✔
337
    if (it.argument) {
298✔
338
      for (const auto& key : it.argument->conflicts_with()) {
524✔
339
        if (!m_keys.count(key)) {
4✔
340
          any_errors = true;
×
341
          log::error() << it.argument->key() << " conflicts with "
×
342
                       << "unknown command-line option " << key;
×
343
        }
344
      }
345

346
      for (const auto& key : it.argument->depends_on()) {
524✔
347
        if (!m_keys.count(key)) {
4✔
348
          any_errors = true;
×
349
          log::error() << it.argument->key() << " requires "
×
350
                       << "unknown command-line option " << key;
×
351
        }
352
      }
353
    }
354
  }
355

356
  AR_REQUIRE(!any_errors, "bugs in argument parsing");
21✔
357
}
21✔
358

359
argument_ptr
360
parser::find_argument(const std::string& key)
25✔
361
{
362
  auto it = m_keys.find(key);
25✔
363
  if (it != m_keys.end()) {
75✔
364
    return it->second;
38✔
365
  }
366

367
  if (key.size() > 1 && key.front() == '-' && key.back() != '-') {
23✔
368
    string_vec candidates;
5✔
369
    const size_t max_distance = 1 + key.size() / 4;
5✔
370

371
    for (const auto& arg : m_args) {
164✔
372
      if (arg.argument) {
72✔
373
        for (const auto& name : arg.argument->keys()) {
229✔
374

375
          if (is_similar_argument(key, name, max_distance)) {
31✔
376
            candidates.push_back(name);
7✔
377
          }
378
        }
21✔
379
      }
380
    }
381

382
    auto error = log::error();
5✔
383
    error << "Unknown argument '" << key << "'";
15✔
384

385
    if (!candidates.empty()) {
10✔
386
      std::sort(candidates.begin(), candidates.end());
12✔
387

388
      error << ". Did you mean " << candidates.front();
12✔
389
      for (size_t i = 1; i < candidates.size() - 1; ++i) {
10✔
390
        error << ", " << candidates.at(i);
4✔
391
      }
392

393
      if (candidates.size() > 1) {
8✔
394
        error << " or " << candidates.back();
6✔
395
      }
396

397
      error << "?";
4✔
398
    }
399
  } else {
5✔
400
    log::error() << "Unexpected positional argument '" << key << "'";
5✔
401
  }
402

403
  return {};
31✔
404
}
405

406
///////////////////////////////////////////////////////////////////////////////
407

408
argument::argument(const std::string& key, std::string metavar)
144✔
409
  : m_key_long(key)
144✔
410
  , m_metavar(std::move(metavar))
144✔
411
  , m_sink(std::make_unique<bool_sink>(&m_default_sink))
1,440✔
412
{
413
  AR_REQUIRE(!key.empty() && key.at(0) == '-', shell_escape(key));
432✔
414
}
144✔
415

416
argument&
417
argument::help(const std::string& text)
99✔
418
{
419
  m_help = text;
99✔
420

421
  return *this;
99✔
422
}
423

424
string_vec
425
argument::keys() const
110✔
426
{
427
  string_vec keys = m_deprecated_keys;
110✔
428
  keys.push_back(m_key_long);
110✔
429

430
  if (!m_key_short.empty()) {
220✔
431
    keys.push_back(m_key_short);
53✔
432
  }
433

434
  return keys;
110✔
435
}
×
436

437
std::string
438
argument::help() const
51✔
439
{
440
  // Append string representation of current (default) value
441
  std::ostringstream ss(m_help);
51✔
442
  ss << m_help;
51✔
443

444
  const auto choices = m_sink->choices();
102✔
445
  if (!choices.empty()) {
102✔
446
    ss << ". Possible values are " << join_text(choices, ", ", ", and ");
15✔
447
  }
448

449
  if (m_sink->has_default() && m_help.find("[default:") == std::string::npos) {
102✔
450
    ss << " [default: " << default_value() << "]";
8✔
451
  }
452

453
  return ss.str();
102✔
454
}
51✔
455

456
size_t
457
argument::min_values() const
92✔
458
{
459
  return m_sink->min_values();
276✔
460
}
461

462
size_t
463
argument::max_values() const
89✔
464
{
465
  return m_sink->max_values();
267✔
466
}
467

468
std::string
469
argument::value() const
3✔
470
{
471
  return m_sink->value();
6✔
472
}
473

474
std::string
475
argument::default_value() const
2✔
476
{
477
  return m_sink->default_value();
4✔
478
}
479

480
template<typename A, typename B>
481
A&
482
bind(std::unique_ptr<sink>& ptr, B* sink)
23✔
483
{
484
  ptr = std::make_unique<A>(sink);
69✔
485

486
  return static_cast<A&>(*ptr);
46✔
487
}
488

5✔
489
bool_sink&
490
argument::bind_bool(bool* ptr)
15✔
491
{
492
  return bind<bool_sink>(m_sink, ptr);
10✔
493
}
494

5✔
495
uint_sink&
496
argument::bind_uint(unsigned* ptr)
15✔
497
{
498
  return bind<uint_sink>(m_sink, ptr);
10✔
499
}
500

2✔
501
double_sink&
502
argument::bind_double(double* ptr)
6✔
503
{
504
  return bind<double_sink>(m_sink, ptr);
4✔
505
}
506

10✔
507
str_sink&
508
argument::bind_str(std::string* ptr)
30✔
509
{
510
  return bind<str_sink>(m_sink, ptr);
20✔
511
}
512

1✔
513
vec_sink&
514
argument::bind_vec(string_vec* ptr)
3✔
515
{
516
  return bind<vec_sink>(m_sink, ptr);
2✔
517
}
518

519
argument&
520
argument::abbreviation(char key)
1✔
521
{
522
  m_key_short.clear();
1✔
523
  m_key_short.push_back('-');
524
  m_key_short.push_back(key);
525

526
  return *this;
10✔
527
}
528

10✔
529
argument&
530
argument::deprecated_alias(const std::string& key)
531
{
532
  AR_REQUIRE(!key.empty() && key.at(0) == '-', shell_escape(key));
2✔
533
  m_deprecated_keys.emplace_back(key);
534

2✔
535
  return *this;
536
}
537

538
argument&
5✔
539
argument::deprecated()
540
{
5✔
541
  m_deprecated = true;
542

543
  return hidden();
544
}
5✔
545

546
argument&
5✔
547
argument::hidden()
548
{
549
  m_hidden = true;
550

59✔
551
  return *this;
552
}
59✔
553

59✔
554
argument&
59✔
555
argument::depends_on(const std::string& key)
556
{
59✔
557
  AR_REQUIRE(!key.empty() && key.at(0) == '-', shell_escape(key));
558
  m_depends_on.push_back(key);
559

560
  return *this;
3✔
561
}
562

9✔
563
argument&
3✔
564
argument::conflicts_with(const std::string& key)
565
{
3✔
566
  AR_REQUIRE(!key.empty() && key.at(0) == '-', shell_escape(key));
567
  m_conflicts_with.push_back(key);
568

569
  return *this;
2✔
570
}
571

2✔
572
void
573
n_args_error(const std::string& key,
2✔
574
             size_t limit,
575
             const char* relation,
576
             size_t n)
577
{
4✔
578
  auto out = log::error();
579

4✔
580
  out << "Command-line argument " << key << " takes" << relation << " " << limit
581
      << " value";
4✔
582

583
  if (limit != 1) {
584
    out << "s";
585
  }
4✔
586

587
  if (n == 1) {
12✔
588
    out << ", but 1 value was provided!";
4✔
589
  } else {
590
    out << ", but " << n << " values were provided!";
4✔
591
  }
592
}
593

594
size_t
4✔
595
argument::parse(string_vec_citer start, const string_vec_citer& end)
596
{
12✔
597
  AR_REQUIRE(start != end);
4✔
598

599
  const bool deprecated_alias = is_deprecated_alias(*start);
4✔
600
  AR_REQUIRE(deprecated_alias || *start == m_key_long ||
601
             (!m_key_short.empty() && *start == m_key_short));
602

603
  if (m_deprecated) {
1✔
604
    log::warn() << "Option " << *start << " is deprecated and will "
605
                << "be removed in the future.";
606
  } else if (deprecated_alias) {
607
    log::warn() << "Option " << *start << " has been renamed to " << key()
608
                << ". Support for the old name will be removed in the future.";
1✔
609
  }
610

6✔
611
  if (m_times_set == 1) {
1✔
612
    log::warn() << "Command-line option " << key()
613
                << " has been specified more than once.";
1✔
614
  }
×
615

616
  double numeric_sink = 0;
617
  auto end_of_values = start + 1;
1✔
618
  for (; end_of_values != end; ++end_of_values) {
×
619
    if (end_of_values->size() > 1 && end_of_values->front() == '-' &&
620
        // Avoid confusing numeric values for command-line arguments
3✔
621
        !to_double(*end_of_values, numeric_sink)) {
622
      break;
2✔
623
    }
624
  }
625

33✔
626
  const auto min_values = m_sink->min_values();
627
  const auto max_values = m_sink->max_values();
66✔
628

629
  AR_REQUIRE(start < end_of_values);
66✔
630
  auto n_values = static_cast<size_t>(end_of_values - start - 1);
77✔
631

632
  if (n_values != min_values && min_values == max_values) {
633
    n_args_error(*start, min_values, "", n_values);
32✔
634

6✔
635
    return parsing_failed;
2✔
636
  } else if (n_values < min_values) {
31✔
637
    n_args_error(*start, min_values, " at least", n_values);
8✔
638

2✔
639
    return parsing_failed;
640
  } else if (n_values > max_values) {
641
    n_args_error(*start, max_values, " at most", n_values);
32✔
642

5✔
643
    return parsing_failed;
2✔
644
  }
645

646
  size_t result = parsing_failed;
32✔
647
  std::string error_message;
32✔
648

94✔
649
  try {
61✔
650
    result = m_sink->consume(start + 1, end_of_values);
651
  } catch (const std::invalid_argument& error) {
5✔
652
    error_message = error.what();
653
  }
654

655
  if (result == parsing_failed) {
656
    auto error = log::error();
64✔
657

64✔
658
    error << "Invalid command-line argument " << *start;
659
    for (auto it = start + 1; it != end_of_values; ++it) {
64✔
660
      error << " " << shell_escape(*it);
32✔
661
    }
662

32✔
663
    if (!error_message.empty()) {
2✔
664
      error << ": " << error_message;
665
    }
666

31✔
667
    return result;
×
668
  } else if (result == invalid_choice) {
669
    auto error = log::error();
670

31✔
671
    error << "Invalid command-line argument " << *start;
×
672
    for (auto it = start + 1; it != end_of_values; ++it) {
673
      error << " " << shell_escape(*it);
674
    }
675

676
    error << ". Valid values for " << *start << " are "
31✔
677
          << join_text(m_sink->choices(), ", ", ", and ");
31✔
678
  }
679

31✔
680
  m_times_set++;
93✔
681
  return result + 1;
1✔
682
}
1✔
683

×
684
bool
685
argument::is_deprecated_alias(const std::string& key) const
31✔
686
{
1✔
687
  return std::find(m_deprecated_keys.begin(), m_deprecated_keys.end(), key) !=
688
         m_deprecated_keys.end();
3✔
689
}
6✔
690

6✔
691
///////////////////////////////////////////////////////////////////////////////
692
// sink
693

2✔
694
sink::sink(size_t n_values)
2✔
695
  : sink(n_values, n_values)
696
{
697
}
1✔
698

31✔
699
sink::sink(size_t min_values, size_t max_values)
×
700
  : m_min_values(min_values)
701
  , m_max_values(max_values)
×
702
{
×
703
}
×
704

705
std::string
706
sink::default_value() const
×
707
{
×
708
  AR_FAIL("sink::default_value not implemented");
709
}
710

30✔
711
std::string
30✔
712
sink::preprocess(std::string value) const
32✔
713
{
714
  if (m_preprocess) {
715
    m_preprocess(value);
38✔
716
  }
717

152✔
718
  return value;
114✔
719
}
720

721
///////////////////////////////////////////////////////////////////////////////
722
// bool_sink
723

724
bool_sink::bool_sink(bool* ptr)
206✔
725
  : sink(0)
×
726
  , m_sink(ptr ? ptr : &m_fallback_sink)
727
{
728
  *m_sink = false;
729
}
222✔
730

222✔
731
std::string
444✔
732
bool_sink::value() const
733
{
734
  return *m_sink ? "on" : "off";
735
}
736

×
737
size_t
738
bool_sink::consume(string_vec_citer start, const string_vec_citer& end)
×
739
{
740
  AR_REQUIRE(start == end);
741
  *m_sink = true;
742

25✔
743
  return 0;
744
}
25✔
745

×
746
///////////////////////////////////////////////////////////////////////////////
747
// uint_sink
748

25✔
749
uint_sink::uint_sink(unsigned* ptr)
750
  : sink(1)
751
  , m_sink(ptr)
752
{
753
  AR_REQUIRE(ptr);
754

150✔
755
  *m_sink = m_default;
756
}
300✔
757

758
uint_sink&
150✔
759
uint_sink::with_default(unsigned value)
5✔
760
{
761
  set_has_default();
762
  *m_sink = m_default = value;
4✔
763

764
  return *this;
10✔
765
}
766

767
std::string
768
uint_sink::value() const
24✔
769
{
770
  return std::to_string(*m_sink);
52✔
771
}
23✔
772

773
std::string
23✔
774
uint_sink::default_value() const
775
{
776
  AR_REQUIRE(has_default());
777
  return std::to_string(m_default);
778
}
779

23✔
780
size_t
781
uint_sink::consume(string_vec_citer start, const string_vec_citer& end)
46✔
782
{
783
  AR_REQUIRE(end - start == 1);
27✔
784

785
  *m_sink = str_to_unsigned(preprocess(*start));
22✔
786

23✔
787
  return 1;
788
}
789

5✔
790
///////////////////////////////////////////////////////////////////////////////
791
// double_sink
5✔
792

5✔
793
double_sink::double_sink(double* ptr)
794
  : sink(1)
5✔
795
  , m_sink(ptr)
796
{
797
  AR_REQUIRE(ptr);
798

3✔
799
  *m_sink = m_default;
800
}
3✔
801

802
double_sink&
803
double_sink::with_default(double value)
804
{
2✔
805
  set_has_default();
806
  *m_sink = m_default = value;
2✔
807

2✔
808
  return *this;
809
}
810

811
namespace {
13✔
812

813
std::string
34✔
814
double_to_string(double value)
815
{
55✔
816
  auto s = std::to_string(value);
817
  s.erase(s.find_last_not_of('0') + 1, std::string::npos);
6✔
818
  s.erase(s.find_last_not_of('.') + 1, std::string::npos);
819

820
  return s;
821
}
822

823
} // namespace
12✔
824

825
std::string
24✔
826
double_sink::default_value() const
827
{
16✔
828
  AR_REQUIRE(has_default());
829
  return double_to_string(m_default);
11✔
830
}
12✔
831

832
std::string
833
double_sink::value() const
2✔
834
{
835
  return double_to_string(*m_sink);
2✔
836
}
2✔
837

838
size_t
2✔
839
double_sink::consume(string_vec_citer start, const string_vec_citer& end)
840
{
841
  AR_REQUIRE(end - start == 1);
842

843
  double value = 0;
844
  std::istringstream stream(*start);
3✔
845
  if (!(stream >> value)) {
846
    throw std::invalid_argument("not a valid number");
3✔
847
  }
3✔
848

3✔
849
  char trailing = 0;
850
  if (stream >> trailing) {
3✔
851
    throw std::invalid_argument("number contains trailing text");
×
852
  }
853

854
  *m_sink = value;
855
  return 1;
856
}
×
857

858
///////////////////////////////////////////////////////////////////////////////
×
859
// str_sink
×
860

861
str_sink::str_sink(std::string* ptr)
862
  : sink(1)
863
  , m_sink(ptr ? ptr : &m_fallback_sink)
3✔
864
{
865
  AR_REQUIRE(m_sink);
3✔
866

867
  *m_sink = m_default;
868
}
869

7✔
870
str_sink&
871
str_sink::with_default(const char* value)
22✔
872
{
873
  set_has_default();
5✔
874
  *m_sink = m_default = value;
10✔
875

15✔
876
  return *this;
1✔
877
}
878

879
str_sink&
4✔
880
str_sink::with_default(const std::string& value)
8✔
881
{
1✔
882
  set_has_default();
883
  *m_sink = m_default = value;
884

3✔
885
  return *this;
3✔
886
}
5✔
887

888
str_sink&
889
str_sink::with_choices(const string_vec& choices)
890
{
891
  m_choices = choices;
21✔
892

893
  return *this;
84✔
894
}
895

21✔
896
std::string
897
str_sink::value() const
21✔
898
{
21✔
899
  return *m_sink;
900
}
901

3✔
902
std::string
903
str_sink::default_value() const
3✔
904
{
6✔
905
  AR_REQUIRE(has_default());
906
  return m_default;
3✔
907
}
908

909
size_t
910
str_sink::consume(string_vec_citer start, const string_vec_citer& end)
2✔
911
{
912
  AR_REQUIRE(end - start == 1);
2✔
913

4✔
914
  if (m_choices.empty()) {
915
    *m_sink = preprocess(*start);
2✔
916
    return 1;
917
  } else {
918
    const auto value = preprocess(*start);
919
    const auto choice = to_lower(value);
8✔
920
    for (const auto& it : m_choices) {
921
      if (choice == to_lower(it)) {
8✔
922
        *m_sink = it;
923
        return 1;
8✔
924
      }
925
    }
926

927
    return invalid_choice;
3✔
928
  }
929
}
3✔
930

931
vec_sink::vec_sink(string_vec* ptr)
932
  : sink(1, std::numeric_limits<size_t>::max())
933
  , m_sink(ptr)
×
934
{
935
  AR_REQUIRE(ptr);
×
936

×
937
  m_sink->clear();
938
}
939

940
vec_sink&
10✔
941
vec_sink::with_min_values(size_t n)
942
{
28✔
943
  AR_REQUIRE(n <= max_values());
944
  set_min_values(n);
16✔
945

20✔
946
  return *this;
5✔
947
}
948

12✔
949
vec_sink&
6✔
950
vec_sink::with_max_values(size_t n)
42✔
951
{
36✔
952
  AR_REQUIRE(n >= min_values());
2✔
953
  set_max_values(n);
2✔
954

955
  return *this;
956
}
957

1✔
958
size_t
6✔
959
vec_sink::consume(string_vec_citer start, const string_vec_citer& end)
960
{
961
  AR_REQUIRE(static_cast<size_t>(end - start) >= min_values());
16✔
962
  AR_REQUIRE(static_cast<size_t>(end - start) <= max_values());
963
  for (auto it = start; it != end; ++it) {
32✔
964
    m_sink->emplace_back(preprocess(*it));
965
  }
20✔
966

967
  return static_cast<size_t>(end - start);
15✔
968
}
16✔
969

970
std::string
971
vec_sink::value() const
5✔
972
{
973
  std::string output;
10✔
974

5✔
975
  for (const auto& s : *m_sink) {
976
    if (!output.empty()) {
5✔
977
      output.push_back(';');
978
    }
979

980
    output.append(shell_escape(s));
3✔
981
  }
982

6✔
983
  return output;
3✔
984
}
985

3✔
986
} // namespace argparse
987

988
} // namespace adapterremoval
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