Skip to content

Commit

Permalink
Easier usage as library - parseExchanges does not require to be last …
Browse files Browse the repository at this point in the history
…anymore
  • Loading branch information
sjanel committed Nov 12, 2023
1 parent 54f60b2 commit 4b022c6
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 68 deletions.
2 changes: 0 additions & 2 deletions src/engine/include/coincentercommands.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ class CoincenterCommands {
// Builds a CoincenterCommands and add commands from given command line options span.
explicit CoincenterCommands(std::span<const CoincenterCmdLineOptions> cmdLineOptionsSpan);

static vector<CoincenterCmdLineOptions> ParseOptions(int argc, const char *argv[]);

/// @brief Set this CoincenterCommands from given command line options.
void addOption(const CoincenterCmdLineOptions &cmdLineOptions, const CoincenterCommand *pPreviousCommand);

Expand Down
1 change: 1 addition & 0 deletions src/engine/include/commandlineoptionsparser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class CommandLineOptionsParser {
public:
using CommandLineOptionType = AllowedCommandLineOptionsBase<OptValueType>::CommandLineOptionType;
using CommandLineOptionWithValue = AllowedCommandLineOptionsBase<OptValueType>::CommandLineOptionWithValue;
using value_type = OptValueType;

template <unsigned N>
explicit CommandLineOptionsParser(const CommandLineOptionWithValue (&init)[N]) {
Expand Down
54 changes: 54 additions & 0 deletions src/engine/include/parseoptions.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#pragma once

#include <filesystem>
#include <iostream>
#include <span>
#include <utility>

#include "cct_vector.hpp"
#include "coincenteroptions.hpp"
#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);

// skip first argument which is program name
CommandLineOptionsParserIterator parserIt(parser, allArguments.last(allArguments.size() - 1U));

using OptValueType = ParserType::value_type;
OptValueType globalOptions;

vector<OptValueType> parsedOptions;

// Support for command line multiple commands. Only full name flags are supported for multi command line commands.
while (parserIt.hasNext()) {
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) {
CoincenterCmdLineOptions::PrintVersion(programName, std::cout);
} else {
// 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
for (auto &groupParsedOptions : parsedOptions) {
groupParsedOptions.mergeGlobalWith(globalOptions);
}

return parsedOptions;
}
} // namespace cct
7 changes: 6 additions & 1 deletion src/engine/include/stringoptionparser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ class StringOptionParser {
enum class AmountType : int8_t { kAbsolute, kPercentage, kNotPresent };
enum class FieldIs : int8_t { kMandatory, kOptional };

/// Constructs an empty StringOptionParser that will not be able to parse anything.
StringOptionParser() noexcept = default;

/// Constructs a StringOptionParser from a full option string.
explicit StringOptionParser(std::string_view optFullStr) : _opt(optFullStr) {}

/// If FieldIs is kOptional and there is no currency, default currency code will be returned.
Expand All @@ -39,8 +41,11 @@ class StringOptionParser {

/// Parse exchanges.
/// Exception will be raised for any invalid exchange name - but an empty list of exchanges is accepted.
ExchangeNames parseExchanges(char sep = ',');
/// 'exchangesSep' and 'endExchangesSep' should be different, otherwise parsing would not be possible
ExchangeNames parseExchanges(char exchangesSep = ',', char endExchangesSep = '\0');

/// Call this method when the end of parsing of this option is expected.
/// If the option has not been fully parsed at this step, exception 'invalid_argument' will be raised.
void checkEndParsing() const;

private:
Expand Down
50 changes: 0 additions & 50 deletions src/engine/src/coincentercommands.cpp
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
#include "coincentercommands.hpp"

#include <chrono>
#include <filesystem>
#include <iostream>
#include <span>
#include <string_view>
#include <utility>

#include "cct_vector.hpp"
#include "coincentercommand.hpp"
#include "coincentercommandfactory.hpp"
#include "coincentercommandtype.hpp"
#include "coincenteroptions.hpp"
#include "coincenteroptionsdef.hpp"
#include "commandlineoptionsparser.hpp"
#include "commandlineoptionsparseriterator.hpp"
#include "currencycode.hpp"
#include "depositsconstraints.hpp"
#include "stringoptionparser.hpp"
Expand All @@ -23,50 +17,6 @@

namespace cct {

vector<CoincenterCmdLineOptions> CoincenterCommands::ParseOptions(int argc, const char *argv[]) {
using OptValueType = CoincenterCmdLineOptions;

auto parser = CommandLineOptionsParser<OptValueType>(CoincenterAllowedOptions<OptValueType>::value);

auto programName = std::filesystem::path(argv[0]).filename().string();

vector<CoincenterCmdLineOptions> parsedOptions;

std::span<const char *> allArguments(argv, argc);
allArguments = allArguments.last(allArguments.size() - 1U); // skip first argument which is program name

CommandLineOptionsParserIterator parserIt(parser, allArguments);

CoincenterCmdLineOptions globalOptions;

// Support for command line multiple commands. Only full name flags are supported for multi command line commands.
while (parserIt.hasNext()) {
std::span<const char *> groupedArguments = parserIt.next();

CoincenterCmdLineOptions 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) {
CoincenterCmdLineOptions::PrintVersion(programName, std::cout);
} else {
// 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
for (CoincenterCmdLineOptions &groupParsedOptions : parsedOptions) {
groupParsedOptions.mergeGlobalWith(globalOptions);
}

return parsedOptions;
}

CoincenterCommands::CoincenterCommands(std::span<const CoincenterCmdLineOptions> cmdLineOptionsSpan) {
_commands.reserve(cmdLineOptionsSpan.size());
const CoincenterCommand *pPreviousCommand = nullptr;
Expand Down
32 changes: 24 additions & 8 deletions src/engine/src/stringoptionparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ std::pair<MonetaryAmount, StringOptionParser::AmountType> StringOptionParser::pa
vector<string> StringOptionParser::getCSVValues() {
vector<string> ret;
if (!_opt.empty()) {
do {
while (true) {
auto nextCommaPos = _opt.find(',', _pos);
if (nextCommaPos == std::string_view::npos) {
nextCommaPos = _opt.size();
Expand All @@ -160,27 +160,43 @@ vector<string> StringOptionParser::getCSVValues() {
break;
}
_pos = nextCommaPos + 1;
} while (true);
}
}
return ret;
}

ExchangeNames StringOptionParser::parseExchanges(char sep) {
std::string_view str(_opt.begin() + _pos, _opt.end());
ExchangeNames StringOptionParser::parseExchanges(char exchangesSep, char endExchangesSep) {
if (exchangesSep == endExchangesSep) {
throw invalid_argument("Exchanges separator cannot be the same as end exchanges separator");
}
auto endPos = _opt.find(endExchangesSep, _pos);
if (endPos == std::string_view::npos) {
endPos = _opt.size();
}
std::string_view str(_opt.begin() + _pos, _opt.begin() + endPos);
ExchangeNames exchanges;
if (!str.empty()) {
std::size_t first;
std::size_t last;
for (first = 0, last = str.find(sep); last != std::string_view::npos; last = str.find(sep, last + 1)) {
std::size_t first = 0;
std::size_t last = str.find(exchangesSep);
for (; last != std::string_view::npos; last = str.find(exchangesSep, last + 1)) {
std::string_view exchangeNameStr(str.begin() + first, str.begin() + last);
if (!ExchangeName::IsValid(exchangeNameStr)) {
return exchanges;
}
exchanges.emplace_back(exchangeNameStr);
first = last + 1;
_pos += exchangeNameStr.size() + 1U;
}
// Add the last one as well
// Add the last one as well, if it is an exchange name
std::string_view exchangeNameStr(str.begin() + first, str.end());
if (!ExchangeName::IsValid(exchangeNameStr)) {
return exchanges;
}
exchanges.emplace_back(exchangeNameStr);
_pos += exchangeNameStr.size();
if (_pos < _opt.size() && _opt[_pos] == endExchangesSep) {
++_pos;
}
}
return exchanges;
}
Expand Down
17 changes: 17 additions & 0 deletions src/engine/test/stringoptionparser_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,21 @@ TEST(StringOptionParserTest, SeveralAmountCurrencyExchangesFlow) {
EXPECT_NO_THROW(parser.checkEndParsing());
}

TEST(StringOptionParserTest, ExchangesNotLast) {
StringOptionParser parser("jst,34.78966544ETH,kucoin_user1-binance-kraken,krw");

EXPECT_EQ(parser.parseCurrency(StringOptionParser::FieldIs::kOptional), CurrencyCode("JST"));
EXPECT_EQ(parser.parseNonZeroAmount(StringOptionParser::FieldIs::kMandatory),
std::make_pair(MonetaryAmount("34.78966544ETH"), StringOptionParser::AmountType::kAbsolute));
EXPECT_EQ(parser.parseNonZeroAmount(StringOptionParser::FieldIs::kOptional),
std::make_pair(MonetaryAmount(), StringOptionParser::AmountType::kNotPresent));
EXPECT_EQ(parser.parseCurrency(StringOptionParser::FieldIs::kOptional), CurrencyCode());

EXPECT_EQ(parser.parseExchanges('-', ','),
ExchangeNames({ExchangeName("kucoin", "user1"), ExchangeName("binance"), ExchangeName("kraken")}));
EXPECT_EQ(parser.parseCurrency(StringOptionParser::FieldIs::kMandatory), CurrencyCode("KRW"));

EXPECT_NO_THROW(parser.checkEndParsing());
}

} // namespace cct
12 changes: 8 additions & 4 deletions src/main/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@

#include "cct_invalid_argument_exception.hpp"
#include "coincentercommands.hpp"
#include "commandlineoptionsparser.hpp"
#include "parseoptions.hpp"
#include "processcommandsfromcli.hpp"
#include "runmodes.hpp"

int main(int argc, const char* argv[]) {
try {
const auto cmdLineOptionsVector = cct::CoincenterCommands::ParseOptions(argc, argv);
using namespace cct;
auto parser =
CommandLineOptionsParser<CoincenterCmdLineOptions>(CoincenterAllowedOptions<CoincenterCmdLineOptions>::value);
const auto cmdLineOptionsVector = ParseOptions(parser, argc, argv);

if (!cmdLineOptionsVector.empty()) {
const cct::CoincenterCommands coincenterCommands(cmdLineOptionsVector);
const CoincenterCommands coincenterCommands(cmdLineOptionsVector);
const auto programName = std::filesystem::path(argv[0]).filename().string();

cct::ProcessCommandsFromCLI(programName, coincenterCommands, cmdLineOptionsVector.front(),
cct::settings::RunMode::kProd);
ProcessCommandsFromCLI(programName, coincenterCommands, cmdLineOptionsVector.front(), settings::RunMode::kProd);
}
} catch (const cct::invalid_argument& e) {
std::cerr << "Invalid argument: " << e.what() << '\n';
Expand Down
5 changes: 2 additions & 3 deletions src/objects/include/exchangename.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@ class ExchangeName {

private:
static constexpr std::size_t kMinExchangeNameLength =
std::ranges::min_element(kSupportedExchanges, [](std::string_view lhs, std::string_view rhs) {
return lhs.size() < rhs.size();
})->size();
std::ranges::min_element(kSupportedExchanges, [](auto lhs, auto rhs) { return lhs.size() < rhs.size(); })
-> size();

std::size_t underscorePos() const { return _nameWithKey.find('_', kMinExchangeNameLength); }

Expand Down
3 changes: 3 additions & 0 deletions src/objects/src/exchangename.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
namespace cct {

bool ExchangeName::IsValid(std::string_view str) {
if (str.size() < kMinExchangeNameLength) {
return false;
}
return std::ranges::any_of(kSupportedExchanges, [lowerStr = ToLower(str)](std::string_view ex) {
return lowerStr.starts_with(ex) && (lowerStr.size() == ex.size() || lowerStr[ex.size()] == '_');
});
Expand Down

0 comments on commit 4b022c6

Please sign in to comment.