Multiply a number by a factor using given mapping. Can be used to write transforms for SIZE or DURATION inputs. Example: With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` one can recognize inputs like "100", "12kb", "100 MB", that will be automatically transformed to 100, 14448, 104857600. Output number type matches the type in the provided mapping. Therefore, if it is required to interpret
| 427 | /// Therefore, if it is required to interpret real inputs like "0.42 s", |
| 428 | /// the mapping should be of a type <string, float> or <string, double>. |
| 429 | class AsNumberWithUnit : public Validator { |
| 430 | public: |
| 431 | /// Adjust AsNumberWithUnit behavior. |
| 432 | /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. |
| 433 | /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError |
| 434 | /// if UNIT_REQUIRED is set and unit literal is not found. |
| 435 | enum Options : std::uint8_t { |
| 436 | CASE_SENSITIVE = 0, |
| 437 | CASE_INSENSITIVE = 1, |
| 438 | UNIT_OPTIONAL = 0, |
| 439 | UNIT_REQUIRED = 2, |
| 440 | DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL |
| 441 | }; |
| 442 | |
| 443 | template <typename Number> |
| 444 | explicit AsNumberWithUnit(std::map<std::string, Number> mapping, |
| 445 | Options opts = DEFAULT, |
| 446 | const std::string &unit_name = "UNIT") { |
| 447 | description(generate_description<Number>(unit_name, opts)); |
| 448 | validate_mapping(mapping, opts); |
| 449 | |
| 450 | // transform function |
| 451 | func_ = [mapping, opts](std::string &input) -> std::string { |
| 452 | Number num{}; |
| 453 | |
| 454 | detail::rtrim(input); |
| 455 | if(input.empty()) { |
| 456 | throw ValidationError("Input is empty"); |
| 457 | } |
| 458 | |
| 459 | // Find split position between number and prefix |
| 460 | auto unit_begin = input.end(); |
| 461 | while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { |
| 462 | --unit_begin; |
| 463 | } |
| 464 | |
| 465 | std::string unit{unit_begin, input.end()}; |
| 466 | input.resize(static_cast<std::size_t>(std::distance(input.begin(), unit_begin))); |
| 467 | detail::trim(input); |
| 468 | |
| 469 | if(opts & UNIT_REQUIRED && unit.empty()) { |
| 470 | throw ValidationError("Missing mandatory unit"); |
| 471 | } |
| 472 | if(opts & CASE_INSENSITIVE) { |
| 473 | unit = detail::to_lower(unit); |
| 474 | } |
| 475 | if(unit.empty()) { |
| 476 | using CLI::detail::lexical_cast; |
| 477 | if(!lexical_cast(input, num)) { |
| 478 | throw ValidationError(std::string("Value ") + input + " could not be converted to " + |
| 479 | detail::type_name<Number>()); |
| 480 | } |
| 481 | // No need to modify input if no unit passed |
| 482 | return {}; |
| 483 | } |
| 484 | |
| 485 | // find corresponding factor |
| 486 | auto it = mapping.find(unit); |
no outgoing calls
no test coverage detected