Skip to content

Commit

Permalink
Clean up and factorize code in Parse options module
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanel committed Mar 23, 2024
1 parent 6697a97 commit 9037946
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 159 deletions.
2 changes: 2 additions & 0 deletions src/api/interface/include/exchange.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class Exchange {
std::string_view name() const { return apiPublic().name(); }
std::string_view keyName() const { return apiPrivate().keyName(); }

std::size_t publicExchangePos() const;

ExchangeName createExchangeName() const {
if (hasPrivateAPI()) {
return {name(), keyName()};
Expand Down
2 changes: 2 additions & 0 deletions src/api/interface/src/exchange.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Exchange::Exchange(const ExchangeConfig &exchangeConfig, ExchangePublic &exchang
_exchangePrivate(std::move(exchangePrivate)),
_pExchangeConfig(std::addressof(exchangeConfig)) {}

std::size_t Exchange::publicExchangePos() const { return PublicExchangePos(name()); }

bool Exchange::canWithdraw(CurrencyCode currencyCode, const CurrencyExchangeFlatSet &currencyExchangeSet) const {
if (_pExchangeConfig->excludedCurrenciesWithdrawal().contains(currencyCode)) {
return false;
Expand Down
5 changes: 3 additions & 2 deletions src/engine/include/coincentercommand.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <cstdint>
#include <optional>
#include <type_traits>
#include <variant>
Expand Down Expand Up @@ -61,7 +62,7 @@ class CoincenterCommand {
MonetaryAmount amount() const { return _amount; }

int depth() const { return _n; }
std::optional<int> optDepth() const { return _n == -1 ? std::nullopt : std::optional<int>(_n); }
auto optDepth() const { return _n == -1 ? std::nullopt : std::optional<int>(_n); }

Market market() const { return _market; }

Expand All @@ -87,7 +88,7 @@ class CoincenterCommand {
MonetaryAmount _amount;
Market _market;
CurrencyCode _cur1, _cur2;
int _n = -1;
int32_t _n = -1;
CoincenterCommandType _type;
bool _isPercentageAmount = false;
bool _withBalanceInUse = false;
Expand Down
167 changes: 90 additions & 77 deletions src/engine/include/commandlineoptionsparser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,35 +49,28 @@ class CommandLineOptionsParser {
CommandLineOptionsParser& append(std::ranges::input_range auto&& opts) {
const auto insertedIt = _opts.insert(_opts.end(), std::ranges::begin(opts), std::ranges::end(opts));
const auto sortByFirst = [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; };

std::sort(insertedIt, _opts.end(), sortByFirst);
std::inplace_merge(_opts.begin(), insertedIt, _opts.end(), sortByFirst);

return *this;
}

OptValueType parse(std::span<const char*> groupedArguments) {
_callbacks.clear();
_callbacks.reserve(_opts.size());
auto parse(std::span<const char* const> groupedArguments) {
OptValueType data;
for (const auto& [cmdLineOption, prop] : _opts) {
_callbacks[cmdLineOption] = registerCallback(cmdLineOption, prop, data);
}

registerCallbacks(data);

const int nbArgs = static_cast<int>(groupedArguments.size());
for (int argPos = 0; argPos < nbArgs; ++argPos) {
std::string_view argStr(groupedArguments[argPos]);
if (std::ranges::none_of(_opts, [argStr](const auto& opt) { return opt.first.matches(argStr); })) {
const auto [possibleOptionIdx, minDistance] = minLevenshteinDistanceOpt(argStr);
auto existingOptionStr = _opts[possibleOptionIdx].first.fullName();

if (minDistance <= 2 ||
minDistance < static_cast<decltype(minDistance)>(std::min(argStr.size(), existingOptionStr.size()) / 2)) {
throw invalid_argument("Unrecognized command-line option '{}' - did you mean '{}'?", argStr,
existingOptionStr);
}
throw invalid_argument("Unrecognized command-line option '{}'", argStr);
if (std::ranges::none_of(_opts, [argStr](const auto& opt) { return opt.first.matches(argStr); })) {
invalidArgument(argStr);
}

for (auto& callback : _callbacks) {
callback.second(argPos, groupedArguments);
for (auto& [_, callback] : _callbacks) {
callback(argPos, groupedArguments);
}
}

Expand Down Expand Up @@ -138,7 +131,8 @@ class CommandLineOptionsParser {

private:
static constexpr std::string_view kEmptyLine =
" "
" "
" "
" ";
static constexpr int kMaxCharLine = kEmptyLine.length();

Expand All @@ -147,7 +141,7 @@ class CommandLineOptionsParser {
template <class>
friend class CommandLineOptionsParserIterator;

using CallbackType = std::function<void(int&, std::span<const char*>)>;
using CallbackType = std::function<void(int&, std::span<const char* const>)>;

[[nodiscard]] bool isOptionValue(std::string_view opt) const {
return std::ranges::none_of(_opts, [opt](const auto& cmdLineOpt) { return cmdLineOpt.first.matches(opt); });
Expand All @@ -162,71 +156,71 @@ class CommandLineOptionsParser {

CallbackType registerCallback(const CommandLineOption& commandLineOption, CommandLineOptionType prop,
OptValueType& data) {
return [this, &commandLineOption, prop, &data](int& idx, std::span<const char*> argv) {
if (commandLineOption.matches(argv[idx])) {
std::visit(overloaded{
// integral value matcher including bool
[&data, &idx, argv, &commandLineOption](std::integral auto OptValueType::*arg) {
using IntType = std::remove_reference_t<decltype(data.*arg)>;
if constexpr (std::is_same_v<IntType, bool>) {
data.*arg = true;
} else {
if (idx + 1U < argv.size()) {
std::string_view nextOpt(argv[idx + 1]);
if (IsOptionInt(nextOpt)) {
data.*arg = FromString<IntType>(nextOpt);
++idx;
return;
}
}
ThrowExpectingValueException(commandLineOption);
}
},
return [this, &commandLineOption, prop, &data](int& idx, std::span<const char* const> argv) {
if (!commandLineOption.matches(argv[idx])) {
return;
}

// CommandLineOptionalInt value matcher
[&data, &idx, argv](CommandLineOptionalInt OptValueType::*arg) {
data.*arg = CommandLineOptionalInt(CommandLineOptionalInt::State::kOptionPresent);
std::visit(overloaded{
// integral value matcher including bool
[&data, &idx, argv, &commandLineOption](std::integral auto OptValueType::*arg) {
using IntType = std::remove_reference_t<decltype(data.*arg)>;
if constexpr (std::is_same_v<IntType, bool>) {
data.*arg = true;
} else {
if (idx + 1U < argv.size()) {
std::string_view nextOpt(argv[idx + 1]);
if (IsOptionInt(nextOpt)) {
data.*arg = FromString<int>(nextOpt);
std::string_view opt(argv[idx + 1]);
if (IsOptionInt(opt)) {
data.*arg = FromString<IntType>(opt);
++idx;
return;
}
}
},

// std::string_view value matcher
[&data, &idx, argv, &commandLineOption](std::string_view OptValueType::*arg) {
if (idx + 1U < argv.size()) {
data.*arg = std::string_view(argv[idx + 1]);
++idx;
} else {
ThrowExpectingValueException(commandLineOption);
}
},

// optional std::string_view value matcher
[this, &data, &idx, argv](std::optional<std::string_view> OptValueType::*arg) {
if (idx + 1U < argv.size() && this->isOptionValue(argv[idx + 1])) {
data.*arg = std::string_view(argv[idx + 1]);
ThrowExpectingValueException(commandLineOption);
}
},

// CommandLineOptionalInt value matcher
[&data, &idx, argv](CommandLineOptionalInt OptValueType::*arg) {
data.*arg = CommandLineOptionalInt(CommandLineOptionalInt::State::kOptionPresent);
if (idx + 1U < argv.size()) {
std::string_view opt(argv[idx + 1]);
if (IsOptionInt(opt)) {
data.*arg = FromString<int>(opt);
++idx;
} else {
data.*arg = std::string_view();
}
},

// duration value matcher
[&data, &idx, argv, &commandLineOption](Duration OptValueType::*arg) {
if (idx + 1U < argv.size()) {
data.*arg = ParseDuration(argv[idx + 1]);
++idx;
} else {
ThrowExpectingValueException(commandLineOption);
}
},
},
prop);
}
}
},

// std::string_view value matcher
[&data, &idx, argv, &commandLineOption](std::string_view OptValueType::*arg) {
if (idx + 1U < argv.size()) {
data.*arg = std::string_view(argv[++idx]);
return;
}
ThrowExpectingValueException(commandLineOption);
},

// optional std::string_view value matcher
[this, &data, &idx, argv](std::optional<std::string_view> OptValueType::*arg) {
if (idx + 1U < argv.size() && this->isOptionValue(argv[idx + 1])) {
data.*arg = std::string_view(argv[idx + 1]);
++idx;
return;
}
data.*arg = std::string_view();
},

// duration value matcher
[&data, &idx, argv, &commandLineOption](Duration OptValueType::*arg) {
if (idx + 1U < argv.size()) {
data.*arg = ParseDuration(argv[++idx]);
return;
}
ThrowExpectingValueException(commandLineOption);
},
},
prop);
};
}

Expand Down Expand Up @@ -262,6 +256,17 @@ class CommandLineOptionsParser {
return lenFirstRows + 3;
}

void invalidArgument(std::string_view argStr) const {
const auto [possibleOptionIdx, minDistance] = minLevenshteinDistanceOpt(argStr);
auto existingOptionStr = _opts[possibleOptionIdx].first.fullName();

if (minDistance <= 2 ||
minDistance < static_cast<decltype(minDistance)>(std::min(argStr.size(), existingOptionStr.size()) / 2)) {
throw invalid_argument("Unrecognized command-line option '{}' - did you mean '{}'?", argStr, existingOptionStr);
}
throw invalid_argument("Unrecognized command-line option '{}'", argStr);
}

std::pair<int, int> minLevenshteinDistanceOpt(std::string_view argStr) const {
vector<int> minDistancesToFullNameOptions(_opts.size());
LevenshteinDistanceCalculator calc;
Expand All @@ -271,6 +276,14 @@ class CommandLineOptionsParser {
return {optIt - minDistancesToFullNameOptions.begin(), *optIt};
}

void registerCallbacks(OptValueType& data) {
_callbacks.reserve(_opts.size());
_callbacks.clear();
for (const auto& [cmdLineOption, prop] : _opts) {
_callbacks[cmdLineOption] = registerCallback(cmdLineOption, prop, data);
}
}

vector<CommandLineOptionWithValue> _opts;
std::unordered_map<CommandLineOption, CallbackType> _callbacks;
};
Expand Down
11 changes: 6 additions & 5 deletions src/engine/include/commandlineoptionsparseriterator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ template <class OptValueType>
class CommandLineOptionsParserIterator {
public:
CommandLineOptionsParserIterator(const CommandLineOptionsParser<OptValueType>& parser,
std::span<const char*> allArguments)
std::span<const char* const> allArguments)
: _parser(parser),
_allArguments(allArguments),
_begIt(_allArguments.begin()),
Expand All @@ -26,16 +26,16 @@ class CommandLineOptionsParserIterator {
* @brief Get next grouped arguments that should be treated together.
* hasNext needs to return true prior to the call to this method
*/
std::span<const char*> next() {
std::span<const char*> ret(_begIt, _endIt);
auto next() {
std::span<const char* const> ret(_begIt, _endIt);
_begIt = _endIt;
_endIt = getNextGroupedEndIt(_endIt);
_hasReturnedAtLeastOneSpan = true;
return ret;
}

private:
using ConstIt = std::span<const char*>::iterator;
using ConstIt = std::span<const char* const>::iterator;

[[nodiscard]] ConstIt getNextGroupedEndIt(ConstIt searchFromIt) const {
if (searchFromIt == _allArguments.end()) {
Expand All @@ -46,6 +46,7 @@ class CommandLineOptionsParserIterator {
for (const auto& [cmdLineOption, _] : _parser._opts) {
std::string_view cmdLineOptionFullName = cmdLineOption.fullName();
if (cmdLineOptionFullName[0] != '-' && cmdLineOptionFullName == optStr) {
// It's a new command name
return searchFromIt;
}
}
Expand All @@ -55,7 +56,7 @@ class CommandLineOptionsParserIterator {
}

const CommandLineOptionsParser<OptValueType>& _parser;
std::span<const char*> _allArguments;
std::span<const char* const> _allArguments;
ConstIt _begIt;
ConstIt _endIt;
bool _hasReturnedAtLeastOneSpan = false;
Expand Down
17 changes: 12 additions & 5 deletions src/engine/include/parseoptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
#include "commandlineoptionsparseriterator.hpp"

namespace cct {

template <class ParserType>
auto ParseOptions(ParserType &parser, int argc, const char *argv[]) {
auto programName = std::filesystem::path(argv[0]).filename().string();

std::span<const char *> allArguments(argv, argc);
std::span<const char *const> allArguments(argv, argc);

// skip first argument which is program name
CommandLineOptionsParserIterator parserIt(parser, allArguments.last(allArguments.size() - 1U));
Expand All @@ -29,19 +30,25 @@ auto ParseOptions(ParserType &parser, int argc, const char *argv[]) {
auto groupedArguments = parserIt.next();

auto groupParsedOptions = parser.parse(groupedArguments);

globalOptions.mergeGlobalWith(groupParsedOptions);

if (groupedArguments.empty()) {
groupParsedOptions.help = true;
}
if (groupParsedOptions.help) {
parser.displayHelp(programName, std::cout);
} else if (groupParsedOptions.version) {
parsedOptions.clear();
break;
}
if (groupParsedOptions.version) {
CoincenterCmdLineOptions::PrintVersion(programName, std::cout);
} else {
// Only store commands if they are not 'help' nor 'version'
parsedOptions.push_back(std::move(groupParsedOptions));
parsedOptions.clear();
break;
}

// Only store commands if they are not 'help' nor 'version'
parsedOptions.push_back(std::move(groupParsedOptions));
}

// Apply global options to all parsed options containing commands
Expand Down
13 changes: 8 additions & 5 deletions src/engine/include/stringoptionparser.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#pragma once

#include <cstddef>
#include <cstdint>
#include <string_view>
#include <utility>
Expand All @@ -26,11 +25,15 @@ class StringOptionParser {

/// If FieldIs is kOptional and there is no currency, default currency code will be returned.
/// otherwise exception invalid_argument will be raised
CurrencyCode parseCurrency(FieldIs fieldIs = FieldIs::kMandatory);
/// @param delimiter defines the expected character (could be not present, which means end of parsing)
/// after the currency
CurrencyCode parseCurrency(FieldIs fieldIs = FieldIs::kMandatory, char delimiter = ',');

/// If FieldIs is kOptional and there is no market, default market will be returned.
/// otherwise exception invalid_argument will be raised
Market parseMarket(FieldIs fieldIs = FieldIs::kMandatory);
/// otherwise exception invalid_argument will be raised.
/// @param delimiter defines the expected character (could be not present, which means end of parsing)
/// after the market
Market parseMarket(FieldIs fieldIs = FieldIs::kMandatory, char delimiter = ',');

/// If FieldIs is kOptional and there is no amount, AmountType kNotPresent will be returned
/// otherwise exception invalid_argument will be raised
Expand All @@ -50,6 +53,6 @@ class StringOptionParser {

private:
std::string_view _opt;
std::size_t _pos{};
std::string_view::size_type _pos{};
};
} // namespace cct
Loading

0 comments on commit 9037946

Please sign in to comment.