diff --git a/src/api/interface/include/exchange.hpp b/src/api/interface/include/exchange.hpp index cfb18540..a542430f 100644 --- a/src/api/interface/include/exchange.hpp +++ b/src/api/interface/include/exchange.hpp @@ -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()}; diff --git a/src/api/interface/src/exchange.cpp b/src/api/interface/src/exchange.cpp index cb86645b..dd50a361 100644 --- a/src/api/interface/src/exchange.cpp +++ b/src/api/interface/src/exchange.cpp @@ -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 ¤cyExchangeSet) const { if (_pExchangeConfig->excludedCurrenciesWithdrawal().contains(currencyCode)) { return false; diff --git a/src/engine/include/coincentercommand.hpp b/src/engine/include/coincentercommand.hpp index 6764b547..0f91f2d8 100644 --- a/src/engine/include/coincentercommand.hpp +++ b/src/engine/include/coincentercommand.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -61,7 +62,7 @@ class CoincenterCommand { MonetaryAmount amount() const { return _amount; } int depth() const { return _n; } - std::optional optDepth() const { return _n == -1 ? std::nullopt : std::optional(_n); } + auto optDepth() const { return _n == -1 ? std::nullopt : std::optional(_n); } Market market() const { return _market; } @@ -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; diff --git a/src/engine/include/commandlineoptionsparser.hpp b/src/engine/include/commandlineoptionsparser.hpp index 99e722f8..b25dcb3c 100644 --- a/src/engine/include/commandlineoptionsparser.hpp +++ b/src/engine/include/commandlineoptionsparser.hpp @@ -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 groupedArguments) { - _callbacks.clear(); - _callbacks.reserve(_opts.size()); + auto parse(std::span groupedArguments) { OptValueType data; - for (const auto& [cmdLineOption, prop] : _opts) { - _callbacks[cmdLineOption] = registerCallback(cmdLineOption, prop, data); - } + + registerCallbacks(data); + const int nbArgs = static_cast(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(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); } } @@ -138,7 +131,8 @@ class CommandLineOptionsParser { private: static constexpr std::string_view kEmptyLine = - " " + " " + " " " "; static constexpr int kMaxCharLine = kEmptyLine.length(); @@ -147,7 +141,7 @@ class CommandLineOptionsParser { template friend class CommandLineOptionsParserIterator; - using CallbackType = std::function)>; + using CallbackType = std::function)>; [[nodiscard]] bool isOptionValue(std::string_view opt) const { return std::ranges::none_of(_opts, [opt](const auto& cmdLineOpt) { return cmdLineOpt.first.matches(opt); }); @@ -162,71 +156,71 @@ class CommandLineOptionsParser { CallbackType registerCallback(const CommandLineOption& commandLineOption, CommandLineOptionType prop, OptValueType& data) { - return [this, &commandLineOption, prop, &data](int& idx, std::span 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; - if constexpr (std::is_same_v) { - data.*arg = true; - } else { - if (idx + 1U < argv.size()) { - std::string_view nextOpt(argv[idx + 1]); - if (IsOptionInt(nextOpt)) { - data.*arg = FromString(nextOpt); - ++idx; - return; - } - } - ThrowExpectingValueException(commandLineOption); - } - }, + return [this, &commandLineOption, prop, &data](int& idx, std::span 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; + if constexpr (std::is_same_v) { + data.*arg = true; + } else { if (idx + 1U < argv.size()) { - std::string_view nextOpt(argv[idx + 1]); - if (IsOptionInt(nextOpt)) { - data.*arg = FromString(nextOpt); + std::string_view opt(argv[idx + 1]); + if (IsOptionInt(opt)) { + data.*arg = FromString(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 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(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 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); }; } @@ -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(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 minLevenshteinDistanceOpt(std::string_view argStr) const { vector minDistancesToFullNameOptions(_opts.size()); LevenshteinDistanceCalculator calc; @@ -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 _opts; std::unordered_map _callbacks; }; diff --git a/src/engine/include/commandlineoptionsparseriterator.hpp b/src/engine/include/commandlineoptionsparseriterator.hpp index 8c87392d..f313836a 100644 --- a/src/engine/include/commandlineoptionsparseriterator.hpp +++ b/src/engine/include/commandlineoptionsparseriterator.hpp @@ -11,7 +11,7 @@ template class CommandLineOptionsParserIterator { public: CommandLineOptionsParserIterator(const CommandLineOptionsParser& parser, - std::span allArguments) + std::span allArguments) : _parser(parser), _allArguments(allArguments), _begIt(_allArguments.begin()), @@ -26,8 +26,8 @@ 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 next() { - std::span ret(_begIt, _endIt); + auto next() { + std::span ret(_begIt, _endIt); _begIt = _endIt; _endIt = getNextGroupedEndIt(_endIt); _hasReturnedAtLeastOneSpan = true; @@ -35,7 +35,7 @@ class CommandLineOptionsParserIterator { } private: - using ConstIt = std::span::iterator; + using ConstIt = std::span::iterator; [[nodiscard]] ConstIt getNextGroupedEndIt(ConstIt searchFromIt) const { if (searchFromIt == _allArguments.end()) { @@ -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; } } @@ -55,7 +56,7 @@ class CommandLineOptionsParserIterator { } const CommandLineOptionsParser& _parser; - std::span _allArguments; + std::span _allArguments; ConstIt _begIt; ConstIt _endIt; bool _hasReturnedAtLeastOneSpan = false; diff --git a/src/engine/include/parseoptions.hpp b/src/engine/include/parseoptions.hpp index f34ada49..5cf158e4 100644 --- a/src/engine/include/parseoptions.hpp +++ b/src/engine/include/parseoptions.hpp @@ -10,11 +10,12 @@ #include "commandlineoptionsparseriterator.hpp" namespace cct { + template auto ParseOptions(ParserType &parser, int argc, const char *argv[]) { auto programName = std::filesystem::path(argv[0]).filename().string(); - std::span allArguments(argv, argc); + std::span allArguments(argv, argc); // skip first argument which is program name CommandLineOptionsParserIterator parserIt(parser, allArguments.last(allArguments.size() - 1U)); @@ -29,6 +30,7 @@ auto ParseOptions(ParserType &parser, int argc, const char *argv[]) { auto groupedArguments = parserIt.next(); auto groupParsedOptions = parser.parse(groupedArguments); + globalOptions.mergeGlobalWith(groupParsedOptions); if (groupedArguments.empty()) { @@ -36,12 +38,17 @@ auto ParseOptions(ParserType &parser, int argc, const char *argv[]) { } 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 diff --git a/src/engine/include/stringoptionparser.hpp b/src/engine/include/stringoptionparser.hpp index eda347e2..58bed922 100644 --- a/src/engine/include/stringoptionparser.hpp +++ b/src/engine/include/stringoptionparser.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -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 @@ -50,6 +53,6 @@ class StringOptionParser { private: std::string_view _opt; - std::size_t _pos{}; + std::string_view::size_type _pos{}; }; } // namespace cct \ No newline at end of file diff --git a/src/engine/src/stringoptionparser.cpp b/src/engine/src/stringoptionparser.cpp index 9eb7813b..11502459 100644 --- a/src/engine/src/stringoptionparser.cpp +++ b/src/engine/src/stringoptionparser.cpp @@ -15,6 +15,47 @@ #include "monetaryamount.hpp" namespace cct { + +// At the end of the currency, either the end of the string or a comma is expected. +CurrencyCode StringOptionParser::parseCurrency(FieldIs fieldIs, char delimiter) { + const auto delimiterPos = _opt.find(delimiter, _pos); + const auto begIt = _opt.begin() + _pos; + const bool isDelimiterPresent = delimiterPos != std::string_view::npos; + const std::string_view tokenStr(begIt, isDelimiterPresent ? _opt.begin() + delimiterPos : _opt.end()); + + if (!tokenStr.empty() && !ExchangeName::IsValid(tokenStr) && CurrencyCode::IsValid(tokenStr)) { + // disambiguate currency code from exchange name + _pos += tokenStr.size(); + if (isDelimiterPresent) { + ++_pos; + } + return tokenStr; + } + if (fieldIs == FieldIs::kMandatory) { + throw invalid_argument("Expected a valid currency code in '{}'", std::string_view(_opt.begin() + _pos, _opt.end())); + } + + return {}; +} + +// At the end of the market, either the end of the string or a comma is expected. +Market StringOptionParser::parseMarket(FieldIs fieldIs, char delimiter) { + const auto oldPos = _pos; + + CurrencyCode firstCur = parseCurrency(fieldIs, '-'); + CurrencyCode secondCur; + + if (firstCur.isDefined()) { + secondCur = parseCurrency(fieldIs, delimiter); + if (secondCur.isNeutral()) { + firstCur = CurrencyCode(); + _pos = oldPos; + } + } + + return {firstCur, secondCur}; +} + namespace { template @@ -39,70 +80,12 @@ std::string_view GetNextStr(std::string_view opt, CharOrStringType sep, std::siz } // namespace -// At the end of the currency, either the end of the string or a comma is expected. -CurrencyCode StringOptionParser::parseCurrency(FieldIs fieldIs) { - const std::size_t commaPos = _opt.find(',', _pos); - const auto begIt = _opt.begin() + _pos; - const bool isCommaPresent = commaPos != std::string_view::npos; - const std::string_view firstStr(begIt, isCommaPresent ? _opt.begin() + commaPos : _opt.end()); - - std::string_view curStr; - if (!firstStr.empty() && !ExchangeName::IsValid(firstStr) && CurrencyCode::IsValid(firstStr)) { - // disambiguate currency code from exchange name - curStr = firstStr; - _pos += curStr.size(); - if (isCommaPresent) { - ++_pos; - } - } else if (fieldIs == FieldIs::kMandatory) { - throw invalid_argument("Expected a valid currency code in '{}'", std::string_view(_opt.begin() + _pos, _opt.end())); - } - return curStr; -} - -// At the end of the market, either the end of the string or a comma is expected. -Market StringOptionParser::parseMarket(FieldIs fieldIs) { - const std::size_t commaPos = _opt.find(',', _pos); - const auto begIt = _opt.begin() + _pos; - const bool isCommaPresent = commaPos != std::string_view::npos; - const std::string_view marketStr(begIt, isCommaPresent ? begIt + commaPos : _opt.end()); - const std::size_t dashPos = marketStr.find('-'); - - if (dashPos == std::string_view::npos) { - if (fieldIs == FieldIs::kMandatory) { - throw invalid_argument("Expected a dash in '{}'", std::string_view(_opt.begin() + _pos, _opt.end())); - } - return {}; - } - std::string_view firstCur(marketStr.begin(), marketStr.begin() + dashPos); - if (!CurrencyCode::IsValid(firstCur)) { - if (fieldIs == FieldIs::kMandatory) { - throw invalid_argument("Expected a valid first currency in '{}'", - std::string_view(_opt.begin() + _pos, _opt.end())); - } - return {}; - } - std::string_view secondCur(marketStr.begin() + dashPos + 1, marketStr.end()); - if (!CurrencyCode::IsValid(secondCur)) { - if (fieldIs == FieldIs::kMandatory) { - throw invalid_argument("Expected a valid second currency in '{}'", - std::string_view(_opt.begin() + _pos, _opt.end())); - } - return {}; - } - _pos += marketStr.size(); - if (isCommaPresent) { - ++_pos; - } - return {CurrencyCode(firstCur), CurrencyCode(secondCur)}; -} - // At the end of the currency, either the end of the string, or a dash or comma is expected. std::pair StringOptionParser::parseNonZeroAmount(FieldIs fieldIs) { - constexpr std::string_view sepWithPercentageAtLast = "-,%"; - std::size_t originalPos = _pos; + static constexpr std::string_view sepWithPercentageAtLast = "-,%"; + const auto originalPos = _pos; auto amountStr = GetNextStr(_opt, sepWithPercentageAtLast, _pos); - std::pair ret{MonetaryAmount(), StringOptionParser::AmountType::kNotPresent}; + auto ret = std::make_pair(MonetaryAmount(), StringOptionParser::AmountType::kNotPresent); if (amountStr.empty()) { if (_pos == _opt.size()) { if (fieldIs == FieldIs::kMandatory) { @@ -179,8 +162,8 @@ ExchangeNames StringOptionParser::parseExchanges(char exchangesSep, char endExch std::string_view str(_opt.begin() + _pos, _opt.begin() + endPos); ExchangeNames exchanges; if (!str.empty()) { - std::size_t first = 0; - std::size_t last = str.find(exchangesSep); + auto last = str.find(exchangesSep); + decltype(last) first = 0; for (; last != std::string_view::npos; last = str.find(exchangesSep, last + 1)) { std::string_view exchangeNameStr(str.begin() + first, str.begin() + last); if (!exchangeNameStr.empty()) { diff --git a/src/http-request/include/curloptions.hpp b/src/http-request/include/curloptions.hpp index 3ddf3742..05cc391e 100644 --- a/src/http-request/include/curloptions.hpp +++ b/src/http-request/include/curloptions.hpp @@ -59,8 +59,8 @@ class CurlOptions { void setHttpHeader(std::string_view key, std::string_view value) { _httpHeaders.set(key, value); } void setHttpHeader(std::string_view key, std::integral auto value) { _httpHeaders.set(key, value); } - using trivially_relocatable = std::integral_constant && - is_trivially_relocatable_v>::type; + using trivially_relocatable = + std::bool_constant && is_trivially_relocatable_v>::type; private: void setPostDataInJsonFormat() { diff --git a/src/http-request/src/curlhandle.cpp b/src/http-request/src/curlhandle.cpp index d04f6065..02ce8281 100644 --- a/src/http-request/src/curlhandle.cpp +++ b/src/http-request/src/curlhandle.cpp @@ -264,6 +264,7 @@ std::string_view CurlHandle::query(std::string_view endpoint, const CurlOptions std::this_thread::sleep_for(sleepingTime); sleepingTime *= 2; } + auto t1 = Clock::now(); // Call diff --git a/src/objects/include/exchangesecretsinfo.hpp b/src/objects/include/exchangesecretsinfo.hpp index c79328b8..7ab7d4ba 100644 --- a/src/objects/include/exchangesecretsinfo.hpp +++ b/src/objects/include/exchangesecretsinfo.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "exchange-names.hpp" namespace cct {