1
1
Fork 0
mirror of https://github.com/oxen-io/lokinet synced 2023-12-14 06:53:00 +01:00

Merge pull request #771 from michael-loki/rcutil

Update rcutil and add to build
This commit is contained in:
Jeff 2019-08-12 18:58:04 -04:00 committed by GitHub
commit 6776b6099e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 574 additions and 681 deletions

View file

@ -286,15 +286,20 @@ if(SHADOW)
else() else()
if(NOT WIN32) if(NOT WIN32)
add_executable(${EXE} ${EXE_SRC}) add_executable(${EXE} ${EXE_SRC})
add_executable(lokinet-rcutil daemon/rcutil.cpp)
elseif(NOT MSVC_VERSION) elseif(NOT MSVC_VERSION)
add_executable(${EXE} ${EXE_SRC} llarp/win32/version.rc) add_executable(${EXE} ${EXE_SRC} llarp/win32/version.rc)
add_executable(lokinet-rcutil daemon/rcutil.cpp llarp/win32/version.rc)
else() else()
add_executable(${EXE} ${EXE_SRC}) add_executable(${EXE} ${EXE_SRC})
add_executable(lokinet-rcutil daemon/rcutil.cpp)
endif(NOT WIN32) endif(NOT WIN32)
add_log_tag(${EXE}) add_log_tag(${EXE})
add_log_tag(lokinet-rcutil)
install(TARGETS ${EXE} RUNTIME DESTINATION bin) install(TARGETS ${EXE} RUNTIME DESTINATION bin)
install(TARGETS lokinet-rcutil RUNTIME DESTINATION bin)
if(WIN32) if(WIN32)
install(PROGRAMS ${CMAKE_SOURCE_DIR}/lokinet-bootstrap.exe DESTINATION bin) install(PROGRAMS ${CMAKE_SOURCE_DIR}/lokinet-bootstrap.exe DESTINATION bin)
else() else()
@ -306,8 +311,10 @@ else()
endif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") endif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
target_link_directories(${EXE} PRIVATE /usr/local/lib) target_link_directories(${EXE} PRIVATE /usr/local/lib)
target_link_directories(lokinet-rcutil PRIVATE /usr/local/lib)
endif() endif()
target_link_libraries(${EXE} PUBLIC ${EXE_LIBS} ${LIBS}) target_link_libraries(${EXE} PUBLIC ${EXE_LIBS} ${LIBS})
target_link_libraries(lokinet-rcutil PUBLIC ${EXE_LIBS} ${LIBS})
if(ANDROID) if(ANDROID)
add_library(${ANDROID_LIB} SHARED jni/lokinet_android.cpp) add_library(${ANDROID_LIB} SHARED jni/lokinet_android.cpp)

View file

@ -1,598 +1,98 @@
#include <buffer.hpp>
#include <crypto/crypto.hpp>
#include <fs.hpp>
#include <llarp.h>
#include <logger.hpp>
#include <messages/dht.hpp>
#include <net.hpp>
#include <nodedb.hpp>
#include <router.hpp>
#include <router_contact.hpp> #include <router_contact.hpp>
#include <time.hpp> #include <util/logger.hpp>
#include <util/ostream_logger.hpp>
#include <fstream> #include <cxxopts.hpp>
#include <getopt.h> #include <string>
#include <signal.h> #include <vector>
struct llarp_main *ctx = 0; bool
dumpRc(const std::vector< std::string >& files, bool json)
void
handle_signal(int sig)
{ {
if(ctx) nlohmann::json result;
llarp_main_signal(ctx, sig); for(const auto& file : files)
}
#ifndef TESTNET
#define TESTNET 0
#endif
void
displayRC(const llarp::RouterContact &rc)
{
std::cout << rc.pubkey << std::endl;
for(const auto &addr : rc.addrs)
{ {
std::cout << "AddressInfo: " << addr << std::endl;
}
}
// fwd declr
struct check_online_request;
void
HandleDHTLocate(llarp_router_lookup_job *job)
{
llarp::LogInfo("DHT result: ", job->found ? "found" : "not found");
if(job->found)
{
// save to nodedb?
displayRC(job->result);
}
// shutdown router
// well because we're in the gotroutermessage, we can't sigint because we'll
// deadlock because we're session locked
// llarp_main_signal(ctx, SIGINT);
// llarp_timer_run(logic->timer, logic->thread);
// we'll we don't want logic thread
// but we want to switch back to the main thread
// llarp_logic_stop();
// still need to exit this logic thread...
llarp_main_abort(ctx);
}
int
main(int argc, char *argv[])
{
// take -c to set location of daemon.ini
// take -o to set log level
// --generate-blank /path/to/file.signed
// --update-ifs /path/to/file.signed
// --key /path/to/long_term_identity.key
// --import
// --export
// --generate /path/to/file.signed
// --update /path/to/file.signed
// --verify /path/to/file.signed
// printf("has [%d]options\n", argc);
if(argc < 2)
{
printf(
"please specify: \n"
"--generate with a path to a router contact file\n"
"--update with a path to a router contact file\n"
"--list path to nodedb skiplist\n"
"--import with a path to a router contact file\n"
"--export a hex formatted public key\n"
"--locate a hex formatted public key\n"
"--find a base32 formatted service address\n"
"--b32 a hex formatted public key\n"
"--hex a base32 formatted public key\n"
"--localInfo \n"
"--read with a path to a router contact file\n"
"--verify with a path to a router contact file\n"
"\n");
return 0;
}
bool haveRequiredOptions = false;
bool genMode = false;
bool updMode = false;
bool listMode = false;
bool importMode = false;
bool exportMode = false;
bool locateMode = false;
bool findMode = false;
bool localMode = false;
bool verifyMode = false;
bool readMode = false;
bool toHexMode = false;
bool toB32Mode = false;
int c;
char *conffname;
char defaultConfName[] = "daemon.ini";
conffname = defaultConfName;
char *rcfname = nullptr;
char *nodesdir = nullptr;
llarp::RouterContact rc; llarp::RouterContact rc;
while(1) const bool ret = rc.Read(file.c_str());
{
static struct option long_options[] = {
{"file", required_argument, 0, 'f'},
{"config", required_argument, 0, 'c'},
{"logLevel", required_argument, 0, 'o'},
{"generate", required_argument, 0, 'g'},
{"update", required_argument, 0, 'u'},
{"list", required_argument, 0, 'l'},
{"import", required_argument, 0, 'i'},
{"export", required_argument, 0, 'e'},
{"locate", required_argument, 0, 'q'},
{"find", required_argument, 0, 'F'},
{"localInfo", no_argument, 0, 'n'},
{"read", required_argument, 0, 'r'},
{"b32", required_argument, 0, 'b'},
{"hex", required_argument, 0, 'h'},
{"verify", required_argument, 0, 'V'},
{0, 0, 0, 0}};
int option_index = 0;
c = getopt_long(argc, argv, "c:f:o:g:lu:i:e:q:F:nr:b:h:V:", long_options,
&option_index);
#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
if(c == -1)
break;
switch(c)
{
case 0:
break;
case 'c':
conffname = optarg;
break;
case 'o':
if(strncmp(optarg, "debug",
MIN(strlen(optarg), static_cast< unsigned long >(5)))
== 0)
{
llarp::SetLogLevel(llarp::eLogDebug);
}
else if(strncmp(optarg, "info",
MIN(strlen(optarg), static_cast< unsigned long >(4)))
== 0)
{
llarp::SetLogLevel(llarp::eLogInfo);
}
else if(strncmp(optarg, "warn",
MIN(strlen(optarg), static_cast< unsigned long >(4)))
== 0)
{
llarp::SetLogLevel(llarp::eLogWarn);
}
else if(strncmp(optarg, "error",
MIN(strlen(optarg), static_cast< unsigned long >(5)))
== 0)
{
llarp::SetLogLevel(llarp::eLogError);
}
break;
case 'V':
rcfname = optarg;
haveRequiredOptions = true;
verifyMode = true;
break;
case 'f':
rcfname = optarg;
break;
case 'l':
nodesdir = optarg;
listMode = true;
break;
case 'i':
// printf ("option -g with value `%s'\n", optarg);
nodesdir = optarg;
importMode = true;
break;
case 'e':
// printf ("option -g with value `%s'\n", optarg);
rcfname = optarg;
exportMode = true;
break;
case 'q':
// printf ("option -g with value `%s'\n", optarg);
rcfname = optarg;
locateMode = true;
break;
case 'F':
rcfname = optarg;
haveRequiredOptions = true;
findMode = true;
break;
case 'g':
// printf ("option -g with value `%s'\n", optarg);
rcfname = optarg;
genMode = true;
break;
case 'u':
// printf ("option -u with value `%s'\n", optarg);
rcfname = optarg;
updMode = true;
break;
case 'n':
localMode = true;
break;
case 'r':
rcfname = optarg;
readMode = true;
break;
case 'b':
rcfname = optarg;
haveRequiredOptions = true;
toB32Mode = true;
break;
case 'h':
rcfname = optarg;
haveRequiredOptions = true;
toHexMode = true;
break;
default:
printf("Bad option: %c\n", c);
return -1;
}
}
#undef MIN
if(!haveRequiredOptions)
{
llarp::LogError("Parameters dont all have their required parameters.\n");
return 0;
}
// printf("parsed options\n");
if(!genMode && !updMode && !listMode && !importMode && !exportMode if(ret)
&& !locateMode && !localMode && !readMode && !findMode && !toB32Mode
&& !toHexMode && !verifyMode)
{ {
llarp::LogError( if(json)
"I don't know what to do, no generate or update parameter\n"); {
return 1; result[file] = rc.ToJson();
} }
else
#ifdef LOKINET_DEBUG
absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort);
#endif
llarp::RouterContact tmp;
if(verifyMode)
{ {
llarp::Crypto crypto(llarp::Crypto::sodium{}); std::cout << "file = " << file << "\n";
if(!rc.Read(rcfname)) std::cout << rc << "\n\n";
{
std::cout << "failed to read " << rcfname << std::endl;
return 1;
}
if(!rc.Verify(&crypto))
{
std::cout << rcfname << " is invalid" << std::endl;
return 1;
}
if(!rc.IsPublicRouter())
{
std::cout << rcfname << " is not a public router";
if(rc.addrs.size() == 0)
{
std::cout << " because it has no public addresses";
}
std::cout << std::endl;
return 1;
}
std::cout << "router identity and dht routing key: " << rc.pubkey
<< std::endl;
std::cout << "router encryption key: " << rc.enckey << std::endl;
if(rc.HasNick())
std::cout << "router nickname: " << rc.Nick() << std::endl;
std::cout << "advertised addresses: " << std::endl;
for(const auto &addr : rc.addrs)
{
std::cout << addr << std::endl;
}
std::cout << std::endl;
std::cout << "advertised exits: ";
if(rc.exits.size())
{
for(const auto &exit : rc.exits)
{
std::cout << exit << std::endl;
} }
} }
else else
{ {
std::cout << "none"; std::cerr << "file = " << file << " was not a valid rc file\n";
} }
std::cout << std::endl;
return 0;
} }
ctx = llarp_main_init(conffname, !TESTNET); if(json)
if(!ctx) std::cout << result << "\n";
{
llarp::LogError("Cant set up context");
return 1;
}
signal(SIGINT, handle_signal);
// is this Neuro or Jeff's?
// this is the only one...
if(listMode)
{
llarp::Crypto crypto(llarp::Crypto::sodium{});
auto nodedb = llarp_nodedb_new(&crypto);
llarp_nodedb_iter itr;
itr.visit = [](llarp_nodedb_iter *i) -> bool {
std::cout << i->rc->pubkey << std::endl;
return true; return true;
}; }
if(llarp_nodedb_load_dir(nodedb, nodesdir) > 0)
llarp_nodedb_iterate_all(nodedb, itr); int
llarp_nodedb_free(&nodedb); main(int argc, char* argv[])
return 0; {
} #ifdef LOKINET_DEBUG
absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort);
if(importMode) #endif
{
if(rcfname == nullptr) // clang-format off
{ cxxopts::Options options(
std::cout << "no file to import" << std::endl; "lokinet-rcutil",
return 1; "LokiNET is a free, open source, private, decentralized, \"market based sybil resistant\" and IP based onion routing network"
} );
llarp::Crypto crypto(llarp::Crypto::sodium{});
auto nodedb = llarp_nodedb_new(&crypto); options.add_options()
if(!llarp_nodedb_ensure_dir(nodesdir)) ("v,verbose", "Verbose", cxxopts::value<bool>())
{ ("h,help", "help", cxxopts::value<bool>())
std::cout << "failed to ensure " << nodesdir << strerror(errno) ("j,json", "output in json", cxxopts::value<bool>())
<< std::endl; ("dump", "dump rc file", cxxopts::value<std::vector<std::string> >(), "FILE");
return 1; // clang-format on
}
llarp_nodedb_set_dir(nodedb, nodesdir); try
if(!rc.Read(rcfname)) {
{ const auto result = options.parse(argc, argv);
std::cout << "failed to read " << rcfname << " " << strerror(errno)
<< std::endl; const bool json = result["json"].as< bool >();
return 1;
} if(result.count("verbose") > 0)
{
if(!rc.Verify(&crypto)) SetLogLevel(llarp::eLogDebug);
{ llarp::LogContext::Instance().logStream =
std::cout << rcfname << " is invalid" << std::endl; std::make_unique< llarp::OStreamLogStream >(std::cerr);
return 1; llarp::LogDebug("debug logging activated");
} }
if(!llarp_nodedb_put_rc(nodedb, rc)) if(result.count("help") > 0)
{ {
std::cout << "failed to store " << strerror(errno) << std::endl; std::cout << options.help() << std::endl;
return 1; return 0;
} }
std::cout << "imported " << rc.pubkey << std::endl; if(result.count("dump") > 0)
{
return 0; if(!dumpRc(result["dump"].as< std::vector< std::string > >(), json))
} {
return 1;
if(genMode) }
{ }
printf("Creating [%s]\n", rcfname); }
// if we zero it out then catch(const cxxopts::OptionParseException& ex)
// set updated timestamp {
rc.last_updated = llarp::time_now_ms(); std::cerr << ex.what() << std::endl;
// load longterm identity std::cout << options.help() << std::endl;
llarp::Crypto crypt(llarp::Crypto::sodium{}); return 1;
}
// which is in daemon.ini config: router.encryption-privkey (defaults
// "encryption.key") return 0;
fs::path encryption_keyfile = "encryption.key";
llarp::SecretKey encryption;
llarp_findOrCreateEncryption(&crypt, encryption_keyfile, encryption);
rc.enckey = llarp::seckey_topublic(encryption);
// get identity public sig key
fs::path ident_keyfile = "identity.key";
llarp::SecretKey identity;
llarp_findOrCreateIdentity(&crypt, ident_keyfile, identity);
rc.pubkey = llarp::seckey_topublic(identity);
// this causes a segfault
if(!rc.Sign(&crypt, identity))
{
std::cout << "failed to sign" << std::endl;
return 1;
}
// set filename
fs::path our_rc_file = rcfname;
// write file
rc.Write(our_rc_file.string().c_str());
// llarp_rc_write(&tmp, our_rc_file.string().c_str());
// release memory for tmp lists
// llarp_rc_free(&tmp);
}
if(updMode)
{
printf("rcutil.cpp - Loading [%s]\n", rcfname);
llarp::RouterContact tmp;
// llarp_rc_clear(&rc);
rc.Clear();
// FIXME: new rc api
// llarp_rc_read(rcfname, &rc);
// set updated timestamp
rc.last_updated = llarp::time_now_ms();
// load longterm identity
llarp::Crypto crypt(llarp::Crypto::sodium{});
// no longer used?
// llarp_crypto_libsodium_init(&crypt);
llarp::SecretKey identityKey; // FIXME: Jeff requests we use this
fs::path ident_keyfile = "identity.key";
llarp::SecretKey identity;
llarp_findOrCreateIdentity(&crypt, ident_keyfile, identity);
// FIXME: update RC API
// get identity public key
// const uint8_t *pubkey = llarp::seckey_topublic(identity);
// FIXME: update RC API
// llarp_rc_set_pubsigkey(&rc, pubkey);
// // FIXME: update RC API
// llarp_rc_sign(&crypt, identity, &rc);
// set filename
fs::path our_rc_file_out = "update_debug.rc";
// write file
// FIXME: update RC API
// rc.Write(our_rc_file.string().c_str());
// llarp_rc_write(&tmp, our_rc_file_out.string().c_str());
}
if(listMode)
{
llarp::Crypto crypto(llarp::Crypto::sodium{});
auto nodedb = llarp_nodedb_new(&crypto);
llarp_nodedb_iter itr;
itr.visit = [](llarp_nodedb_iter *i) -> bool {
std::cout << llarp::PubKey(i->rc->pubkey) << std::endl;
return true;
};
if(llarp_nodedb_load_dir(nodedb, nodesdir) > 0)
llarp_nodedb_iterate_all(nodedb, itr);
llarp_nodedb_free(&nodedb);
return 0;
}
if(exportMode)
{
llarp_main_loadDatabase(ctx);
// llarp::LogInfo("Looking for string: ", rcfname);
llarp::PubKey binaryPK;
llarp::HexDecode(rcfname, binaryPK.data(), binaryPK.size());
llarp::LogInfo("Looking for binary: ", binaryPK);
llarp::RouterContact *rc = llarp_main_getDatabase(ctx, binaryPK.data());
if(!rc)
{
llarp::LogError("Can't load RC from database");
}
std::string filename(rcfname);
filename.append(".signed");
llarp::LogInfo("Writing out: ", filename);
// FIXME: update RC API
// rc.Write(our_rc_file.string().c_str());
// llarp_rc_write(rc, filename.c_str());
}
if(locateMode)
{
llarp::LogInfo("Going online");
llarp_main_setup(ctx);
llarp::PubKey binaryPK;
llarp::HexDecode(rcfname, binaryPK.data(), binaryPK.size());
llarp::LogInfo("Queueing job");
llarp_router_lookup_job *job = new llarp_router_lookup_job;
job->iterative = true;
job->found = false;
job->hook = &HandleDHTLocate;
// llarp_rc_new(&job->result);
job->target = binaryPK; // set job's target
// create query DHT request
check_online_request *request = new check_online_request;
request->ptr = ctx;
request->job = job;
request->online = false;
request->nodes = 0;
request->first = false;
llarp_main_queryDHT(request);
llarp::LogInfo("Processing");
// run system and wait
llarp_main_run(ctx);
}
if(findMode)
{
llarp::LogInfo("Going online");
llarp_main_setup(ctx);
llarp::LogInfo("Please find ", rcfname);
std::string str(rcfname);
llarp::service::Tag tag(rcfname);
llarp::LogInfo("Tag ", tag);
llarp::service::Address addr;
str = str.append(".loki");
llarp::LogInfo("Prestring ", str);
bool res = addr.FromString(str.c_str());
llarp::LogInfo(res ? "Success" : "not a base32 string");
// Base32Decode(rcfname, addr);
llarp::LogInfo("Addr ", addr);
// uint64_t txid, const llarp::service::Address& addr
// FIXME: new API?
// msg->M.push_back(new llarp::dht::FindIntroMessage(tag, 1));
// I guess we may need a router to get any replies
llarp::LogInfo("Processing");
// run system and wait
llarp_main_run(ctx);
}
if(localMode)
{
// FIXME: update llarp_main_getLocalRC
// llarp::RouterContact *rc = llarp_main_getLocalRC(ctx);
// displayRC(rc);
// delete it
}
{
if(rc.Read(rcfname))
displayRC(rc);
}
if(toB32Mode)
{
llarp::LogInfo("Converting hex string ", rcfname);
std::string str(rcfname);
llarp::PubKey binaryPK;
// llarp::service::Address::FromString
llarp::HexDecode(rcfname, binaryPK.data(), binaryPK.size());
char tmp[(1 + 32) * 2] = {0};
std::string b32 = llarp::Base32Encode(binaryPK, tmp);
llarp::LogInfo("to base32 ", b32);
}
if(toHexMode)
{
llarp::service::Address addr;
llarp::Base32Decode(rcfname, addr);
llarp::LogInfo("Converting base32 string ", addr);
// llarp::service::Address::ToString
char ftmp[68] = {0};
const char *hexname =
llarp::HexEncode< llarp::service::Address, decltype(ftmp) >(addr, ftmp);
llarp::LogInfo("to hex ", hexname);
}
// it's a unique_ptr, should clean up itself
// llarp_main_free(ctx);
return 0; // success
} }

View file

@ -633,8 +633,7 @@ namespace libuv
Loop::CloseAll() Loop::CloseAll()
{ {
llarp::LogInfo("Closing all handles"); llarp::LogInfo("Closing all handles");
uv_walk( uv_walk(m_Impl.get(),
m_Impl.get(),
[](uv_handle_t* h, void*) { [](uv_handle_t* h, void*) {
if(uv_is_closing(h)) if(uv_is_closing(h))
return; return;

View file

@ -164,8 +164,7 @@ namespace llarp
HopHandler_ptr HopHandler_ptr
PathContext::GetByUpstream(const RouterID& remote, const PathID_t& id) PathContext::GetByUpstream(const RouterID& remote, const PathID_t& id)
{ {
auto own = MapGet( auto own = MapGet(m_OurPaths, id,
m_OurPaths, id,
[](const PathSet_ptr) -> bool { [](const PathSet_ptr) -> bool {
// TODO: is this right? // TODO: is this right?
return true; return true;

View file

@ -10,6 +10,7 @@
#include <util/status.hpp> #include <util/status.hpp>
#include <functional> #include <functional>
#include <nlohmann/json.hpp>
#include <vector> #include <vector>
#define MAX_RC_SIZE (1024) #define MAX_RC_SIZE (1024)
@ -107,6 +108,12 @@ namespace llarp
util::StatusObject util::StatusObject
ExtractStatus() const; ExtractStatus() const;
nlohmann::json
ToJson() const
{
return ExtractStatus().get();
}
bool bool
BEncode(llarp_buffer_t *buf) const; BEncode(llarp_buffer_t *buf) const;

View file

@ -3,16 +3,29 @@
This is the changelog for `cxxopts`, a C++11 library for parsing command line This is the changelog for `cxxopts`, a C++11 library for parsing command line
options. The project adheres to semantic versioning. options. The project adheres to semantic versioning.
## Next version
### Changed
* Only search for a C++ compiler in CMakeLists.txt.
* Allow for exceptions to be disabled.
* Fix duplicate default options when there is a short and long option.
* Add `CXXOPTS_NO_EXCEPTIONS` to disable exceptions.
## 2.2 ## 2.2
### Changed ### Changed
* Allow integers to have leading zeroes. * Allow integers to have leading zeroes.
* Build the tests by default. * Build the tests by default.
* Don't check for container when showing positional help.
### Added ### Added
* Iterator inputs to `parse_positional`. * Iterator inputs to `parse_positional`.
* Throw an exception if the option in `parse_positional` doesn't exist.
* Parse a delimited list in a single argument for vector options.
* Add an option to disable implicit value on booleans.
### Bug Fixes ### Bug Fixes
@ -22,6 +35,7 @@ options. The project adheres to semantic versioning.
* Throw on invalid option syntax when beginning with a `-`. * Throw on invalid option syntax when beginning with a `-`.
* Throw in `as` when option wasn't present. * Throw in `as` when option wasn't present.
* Fix catching exceptions by reference. * Fix catching exceptions by reference.
* Fix out of bounds errors parsing integers.
## 2.1.1 ## 2.1.1

View file

@ -30,12 +30,13 @@ endforeach()
set(VERSION ${CXXOPTS__VERSION_MAJOR}.${CXXOPTS__VERSION_MINOR}.${CXXOPTS__VERSION_PATCH}) set(VERSION ${CXXOPTS__VERSION_MAJOR}.${CXXOPTS__VERSION_MINOR}.${CXXOPTS__VERSION_PATCH})
message(STATUS "cxxopts version ${VERSION}") message(STATUS "cxxopts version ${VERSION}")
project(cxxopts VERSION "${VERSION}") project(cxxopts VERSION "${VERSION}" LANGUAGES CXX)
enable_testing() enable_testing()
option(CXXOPTS_BUILD_EXAMPLES "Set to ON to build examples" ON) option(CXXOPTS_BUILD_EXAMPLES "Set to ON to build examples" ON)
option(CXXOPTS_BUILD_TESTS "Set to ON to build tests" ON) option(CXXOPTS_BUILD_TESTS "Set to ON to build tests" ON)
option(CXXOPTS_ENABLE_INSTALL "Generate the install target" ON)
# request c++11 without gnu extension for the whole project and enable more warnings # request c++11 without gnu extension for the whole project and enable more warnings
if (CXXOPTS_CXX_STANDARD) if (CXXOPTS_CXX_STANDARD)
@ -53,6 +54,7 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES
endif() endif()
add_library(cxxopts INTERFACE) add_library(cxxopts INTERFACE)
add_library(cxxopts::cxxopts ALIAS cxxopts)
# optionally, enable unicode support using the ICU library # optionally, enable unicode support using the ICU library
set(CXXOPTS_USE_UNICODE_HELP FALSE CACHE BOOL "Use ICU Unicode library") set(CXXOPTS_USE_UNICODE_HELP FALSE CACHE BOOL "Use ICU Unicode library")
@ -70,35 +72,37 @@ target_include_directories(cxxopts INTERFACE
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:include>
) )
include(CMakePackageConfigHelpers) if(CXXOPTS_ENABLE_INSTALL)
set(CXXOPTS_CMAKE_DIR "lib/cmake/cxxopts" CACHE STRING include(CMakePackageConfigHelpers)
set(CXXOPTS_CMAKE_DIR "lib/cmake/cxxopts" CACHE STRING
"Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.")
set(version_config "${PROJECT_BINARY_DIR}/cxxopts-config-version.cmake") set(version_config "${PROJECT_BINARY_DIR}/cxxopts-config-version.cmake")
set(project_config "${PROJECT_BINARY_DIR}/cxxopts-config.cmake") set(project_config "${PROJECT_BINARY_DIR}/cxxopts-config.cmake")
set(targets_export_name cxxopts-targets) set(targets_export_name cxxopts-targets)
# Generate the version, config and target files into the build directory. # Generate the version, config and target files into the build directory.
write_basic_package_version_file( write_basic_package_version_file(
${version_config} ${version_config}
VERSION ${VERSION} VERSION ${VERSION}
COMPATIBILITY AnyNewerVersion) COMPATIBILITY AnyNewerVersion)
configure_package_config_file( configure_package_config_file(
${PROJECT_SOURCE_DIR}/cxxopts-config.cmake.in ${PROJECT_SOURCE_DIR}/cxxopts-config.cmake.in
${project_config} ${project_config}
INSTALL_DESTINATION ${CXXOPTS_CMAKE_DIR}) INSTALL_DESTINATION ${CXXOPTS_CMAKE_DIR})
export(TARGETS cxxopts NAMESPACE cxxopts:: export(TARGETS cxxopts NAMESPACE cxxopts::
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
# Install version, config and target files. # Install version, config and target files.
#install( install(
# FILES ${project_config} ${version_config} FILES ${project_config} ${version_config}
# DESTINATION ${CXXOPTS_CMAKE_DIR}) DESTINATION ${CXXOPTS_CMAKE_DIR})
#install(EXPORT ${targets_export_name} DESTINATION ${CXXOPTS_CMAKE_DIR} install(EXPORT ${targets_export_name} DESTINATION ${CXXOPTS_CMAKE_DIR}
# NAMESPACE cxxopts::) NAMESPACE cxxopts::)
# Install the header file and export the target # Install the header file and export the target
#install(TARGETS cxxopts EXPORT ${targets_export_name} DESTINATION lib) install(TARGETS cxxopts EXPORT ${targets_export_name} DESTINATION lib)
#install(FILES ${PROJECT_SOURCE_DIR}/include/cxxopts.hpp DESTINATION include) install(FILES ${PROJECT_SOURCE_DIR}/include/cxxopts.hpp DESTINATION include)
endif()
add_subdirectory(src) add_subdirectory(src)
add_subdirectory(test) add_subdirectory(test)

View file

@ -116,6 +116,18 @@ There is no way to disambiguate positional arguments from the value following
a boolean, so we have chosen that they will be positional arguments, and a boolean, so we have chosen that they will be positional arguments, and
therefore, `-o false` does not work. therefore, `-o false` does not work.
## `std::vector<T>` values
Parsing of list of values in form of an `std::vector<T>` is also supported, as long as `T`
can be parsed. To separate single values in a list the definition `CXXOPTS_VECTOR_DELIMITER`
is used, which is ',' by default. Ensure that you use no whitespaces between values because
those would be interpreted as the next command line option. Example for a command line option
that can be parsed as a `std::vector<double>`:
~~~
--my_list=1,-2.1,3,4.5
~~~
## Custom help ## Custom help
The string after the program name on the first line of the help can be The string after the program name on the first line of the help can be

View file

@ -29,6 +29,7 @@ THE SOFTWARE.
#include <cctype> #include <cctype>
#include <exception> #include <exception>
#include <iostream> #include <iostream>
#include <limits>
#include <map> #include <map>
#include <memory> #include <memory>
#include <regex> #include <regex>
@ -43,6 +44,10 @@ THE SOFTWARE.
#define CXXOPTS_HAS_OPTIONAL #define CXXOPTS_HAS_OPTIONAL
#endif #endif
#ifndef CXXOPTS_VECTOR_DELIMITER
#define CXXOPTS_VECTOR_DELIMITER ','
#endif
#define CXXOPTS__VERSION_MAJOR 2 #define CXXOPTS__VERSION_MAJOR 2
#define CXXOPTS__VERSION_MINOR 2 #define CXXOPTS__VERSION_MINOR 2
#define CXXOPTS__VERSION_PATCH 0 #define CXXOPTS__VERSION_PATCH 0
@ -309,6 +314,9 @@ namespace cxxopts
virtual std::shared_ptr<Value> virtual std::shared_ptr<Value>
implicit_value(const std::string& value) = 0; implicit_value(const std::string& value) = 0;
virtual std::shared_ptr<Value>
no_implicit_value() = 0;
virtual bool virtual bool
is_boolean() const = 0; is_boolean() const = 0;
}; };
@ -354,7 +362,7 @@ namespace cxxopts
{ {
public: public:
option_exists_error(const std::string& option) option_exists_error(const std::string& option)
: OptionSpecException(u8"Option " + LQUOTE + option + RQUOTE + u8" already exists") : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists")
{ {
} }
}; };
@ -363,7 +371,7 @@ namespace cxxopts
{ {
public: public:
invalid_option_format_error(const std::string& format) invalid_option_format_error(const std::string& format)
: OptionSpecException(u8"Invalid option format " + LQUOTE + format + RQUOTE) : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE)
{ {
} }
}; };
@ -371,8 +379,8 @@ namespace cxxopts
class option_syntax_exception : public OptionParseException { class option_syntax_exception : public OptionParseException {
public: public:
option_syntax_exception(const std::string& text) option_syntax_exception(const std::string& text)
: OptionParseException(u8"Argument " + LQUOTE + text + RQUOTE + : OptionParseException("Argument " + LQUOTE + text + RQUOTE +
u8" starts with a - but has incorrect syntax") " starts with a - but has incorrect syntax")
{ {
} }
}; };
@ -381,7 +389,7 @@ namespace cxxopts
{ {
public: public:
option_not_exists_exception(const std::string& option) option_not_exists_exception(const std::string& option)
: OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + u8" does not exist") : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist")
{ {
} }
}; };
@ -391,7 +399,7 @@ namespace cxxopts
public: public:
missing_argument_exception(const std::string& option) missing_argument_exception(const std::string& option)
: OptionParseException( : OptionParseException(
u8"Option " + LQUOTE + option + RQUOTE + u8" is missing an argument" "Option " + LQUOTE + option + RQUOTE + " is missing an argument"
) )
{ {
} }
@ -402,7 +410,7 @@ namespace cxxopts
public: public:
option_requires_argument_exception(const std::string& option) option_requires_argument_exception(const std::string& option)
: OptionParseException( : OptionParseException(
u8"Option " + LQUOTE + option + RQUOTE + u8" requires an argument" "Option " + LQUOTE + option + RQUOTE + " requires an argument"
) )
{ {
} }
@ -417,8 +425,8 @@ namespace cxxopts
const std::string& arg const std::string& arg
) )
: OptionParseException( : OptionParseException(
u8"Option " + LQUOTE + option + RQUOTE + "Option " + LQUOTE + option + RQUOTE +
u8" does not take an argument, but argument " + " does not take an argument, but argument " +
LQUOTE + arg + RQUOTE + " given" LQUOTE + arg + RQUOTE + " given"
) )
{ {
@ -429,7 +437,7 @@ namespace cxxopts
{ {
public: public:
option_not_present_exception(const std::string& option) option_not_present_exception(const std::string& option)
: OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + u8" not present") : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present")
{ {
} }
}; };
@ -442,7 +450,7 @@ namespace cxxopts
const std::string& arg const std::string& arg
) )
: OptionParseException( : OptionParseException(
u8"Argument " + LQUOTE + arg + RQUOTE + u8" failed to parse" "Argument " + LQUOTE + arg + RQUOTE + " failed to parse"
) )
{ {
} }
@ -453,12 +461,32 @@ namespace cxxopts
public: public:
option_required_exception(const std::string& option) option_required_exception(const std::string& option)
: OptionParseException( : OptionParseException(
u8"Option " + LQUOTE + option + RQUOTE + u8" is required but not present" "Option " + LQUOTE + option + RQUOTE + " is required but not present"
) )
{ {
} }
}; };
template <typename T>
void throw_or_mimic(const std::string& text)
{
static_assert(std::is_base_of<std::exception, T>::value,
"throw_or_mimic only works on std::exception and "
"deriving classes");
#ifndef CXXOPTS_NO_EXCEPTIONS
// If CXXOPTS_NO_EXCEPTIONS is not defined, just throw
throw T{text};
#else
// Otherwise manually instantiate the exception, print what() to stderr,
// and abort
T exception{text};
std::cerr << exception.what() << std::endl;
std::cerr << "Aborting (exceptions disabled)..." << std::endl;
std::abort();
#endif
}
namespace values namespace values
{ {
namespace namespace
@ -466,9 +494,9 @@ namespace cxxopts
std::basic_regex<char> integer_pattern std::basic_regex<char> integer_pattern
("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)");
std::basic_regex<char> truthy_pattern std::basic_regex<char> truthy_pattern
("(t|T)(rue)?"); ("(t|T)(rue)?|1");
std::basic_regex<char> falsy_pattern std::basic_regex<char> falsy_pattern
("((f|F)(alse)?)?"); ("(f|F)(alse)?|0");
} }
namespace detail namespace detail
@ -485,16 +513,16 @@ namespace cxxopts
{ {
if (negative) if (negative)
{ {
if (u > static_cast<U>(-(std::numeric_limits<T>::min)())) if (u > static_cast<U>((std::numeric_limits<T>::min)()))
{ {
throw argument_incorrect_type(text); throw_or_mimic<argument_incorrect_type>(text);
} }
} }
else else
{ {
if (u > static_cast<U>((std::numeric_limits<T>::max)())) if (u > static_cast<U>((std::numeric_limits<T>::max)()))
{ {
throw argument_incorrect_type(text); throw_or_mimic<argument_incorrect_type>(text);
} }
} }
} }
@ -523,14 +551,15 @@ namespace cxxopts
// if we got to here, then `t` is a positive number that fits into // if we got to here, then `t` is a positive number that fits into
// `R`. So to avoid MSVC C4146, we first cast it to `R`. // `R`. So to avoid MSVC C4146, we first cast it to `R`.
// See https://github.com/jarro2783/cxxopts/issues/62 for more details. // See https://github.com/jarro2783/cxxopts/issues/62 for more details.
return -static_cast<R>(t); return -static_cast<R>(t-1)-1;
} }
template <typename R, typename T> template <typename R, typename T>
T T
checked_negate(T&&, const std::string& text, std::false_type) checked_negate(T&& t, const std::string& text, std::false_type)
{ {
throw argument_incorrect_type(text); throw_or_mimic<argument_incorrect_type>(text);
return t;
} }
template <typename T> template <typename T>
@ -542,7 +571,7 @@ namespace cxxopts
if (match.length() == 0) if (match.length() == 0)
{ {
throw argument_incorrect_type(text); throw_or_mimic<argument_incorrect_type>(text);
} }
if (match.length(4) > 0) if (match.length(4) > 0)
@ -553,7 +582,6 @@ namespace cxxopts
using US = typename std::make_unsigned<T>::type; using US = typename std::make_unsigned<T>::type;
constexpr auto umax = (std::numeric_limits<US>::max)();
constexpr bool is_signed = std::numeric_limits<T>::is_signed; constexpr bool is_signed = std::numeric_limits<T>::is_signed;
const bool negative = match.length(1) > 0; const bool negative = match.length(1) > 0;
const uint8_t base = match.length(2) > 0 ? 16 : 10; const uint8_t base = match.length(2) > 0 ? 16 : 10;
@ -568,27 +596,28 @@ namespace cxxopts
if (*iter >= '0' && *iter <= '9') if (*iter >= '0' && *iter <= '9')
{ {
digit = *iter - '0'; digit = static_cast<US>(*iter - '0');
} }
else if (base == 16 && *iter >= 'a' && *iter <= 'f') else if (base == 16 && *iter >= 'a' && *iter <= 'f')
{ {
digit = *iter - 'a' + 10; digit = static_cast<US>(*iter - 'a' + 10);
} }
else if (base == 16 && *iter >= 'A' && *iter <= 'F') else if (base == 16 && *iter >= 'A' && *iter <= 'F')
{ {
digit = *iter - 'A' + 10; digit = static_cast<US>(*iter - 'A' + 10);
} }
else else
{ {
throw argument_incorrect_type(text); throw_or_mimic<argument_incorrect_type>(text);
} }
if (umax - digit < result * base) US next = result * base + digit;
if (result > next)
{ {
throw argument_incorrect_type(text); throw_or_mimic<argument_incorrect_type>(text);
} }
result = result * base + digit; result = next;
} }
detail::check_signed_range<T>(negative, result, text); detail::check_signed_range<T>(negative, result, text);
@ -601,7 +630,7 @@ namespace cxxopts
} }
else else
{ {
value = result; value = static_cast<T>(result);
} }
} }
@ -611,7 +640,7 @@ namespace cxxopts
std::stringstream in(text); std::stringstream in(text);
in >> value; in >> value;
if (!in) { if (!in) {
throw argument_incorrect_type(text); throw_or_mimic<argument_incorrect_type>(text);
} }
} }
@ -691,7 +720,7 @@ namespace cxxopts
return; return;
} }
throw argument_incorrect_type(text); throw_or_mimic<argument_incorrect_type>(text);
} }
inline inline
@ -714,9 +743,13 @@ namespace cxxopts
void void
parse_value(const std::string& text, std::vector<T>& value) parse_value(const std::string& text, std::vector<T>& value)
{ {
std::stringstream in(text);
std::string token;
while(in.eof() == false && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
T v; T v;
parse_value(text, v); parse_value(token, v);
value.push_back(v); value.emplace_back(std::move(v));
}
} }
#ifdef CXXOPTS_HAS_OPTIONAL #ifdef CXXOPTS_HAS_OPTIONAL
@ -825,6 +858,13 @@ namespace cxxopts
return shared_from_this(); return shared_from_this();
} }
std::shared_ptr<Value>
no_implicit_value()
{
m_implicit = false;
return shared_from_this();
}
std::string std::string
get_default_value() const get_default_value() const
{ {
@ -1035,21 +1075,29 @@ namespace cxxopts
parse_default(std::shared_ptr<const OptionDetails> details) parse_default(std::shared_ptr<const OptionDetails> details)
{ {
ensure_value(details); ensure_value(details);
m_default = true;
m_value->parse(); m_value->parse();
} }
size_t size_t
count() const count() const noexcept
{ {
return m_count; return m_count;
} }
// TODO: maybe default options should count towards the number of arguments
bool
has_default() const noexcept
{
return m_default;
}
template <typename T> template <typename T>
const T& const T&
as() const as() const
{ {
if (m_value == nullptr) { if (m_value == nullptr) {
throw std::domain_error("No value"); throw_or_mimic<std::domain_error>("No value");
} }
#ifdef CXXOPTS_NO_RTTI #ifdef CXXOPTS_NO_RTTI
@ -1071,6 +1119,7 @@ namespace cxxopts
std::shared_ptr<Value> m_value; std::shared_ptr<Value> m_value;
size_t m_count = 0; size_t m_count = 0;
bool m_default = false;
}; };
class KeyValue class KeyValue
@ -1143,7 +1192,7 @@ namespace cxxopts
if (iter == m_options->end()) if (iter == m_options->end())
{ {
throw option_not_present_exception(option); throw_or_mimic<option_not_present_exception>(option);
} }
auto riter = m_results.find(iter->second); auto riter = m_results.find(iter->second);
@ -1202,6 +1251,28 @@ namespace cxxopts
std::vector<KeyValue> m_sequential; std::vector<KeyValue> m_sequential;
}; };
struct Option
{
Option
(
const std::string& opts,
const std::string& desc,
const std::shared_ptr<const Value>& value = ::cxxopts::value<bool>(),
const std::string& arg_help = ""
)
: opts_(opts)
, desc_(desc)
, value_(value)
, arg_help_(arg_help)
{
}
std::string opts_;
std::string desc_;
std::shared_ptr<const Value> value_;
std::string arg_help_;
};
class Options class Options
{ {
typedef std::unordered_map<std::string, std::shared_ptr<OptionDetails>> typedef std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
@ -1254,6 +1325,20 @@ namespace cxxopts
OptionAdder OptionAdder
add_options(std::string group = ""); add_options(std::string group = "");
void
add_options
(
const std::string& group,
std::initializer_list<Option> options
);
void
add_option
(
const std::string& group,
const Option& option
);
void void
add_option add_option
( (
@ -1492,6 +1577,21 @@ ParseResult::ParseResult
parse(argc, argv); parse(argc, argv);
} }
inline
void
Options::add_options
(
const std::string &group,
std::initializer_list<Option> options
)
{
OptionAdder option_adder(*this, group);
for (const auto &option: options)
{
option_adder(option.opts_, option.desc_, option.value_, option.arg_help_);
}
}
inline inline
OptionAdder OptionAdder
Options::add_options(std::string group) Options::add_options(std::string group)
@ -1514,7 +1614,7 @@ OptionAdder::operator()
if (result.empty()) if (result.empty())
{ {
throw invalid_option_format_error(opts); throw_or_mimic<invalid_option_format_error>(opts);
} }
const auto& short_match = result[2]; const auto& short_match = result[2];
@ -1522,10 +1622,10 @@ OptionAdder::operator()
if (!short_match.length() && !long_match.length()) if (!short_match.length() && !long_match.length())
{ {
throw invalid_option_format_error(opts); throw_or_mimic<invalid_option_format_error>(opts);
} else if (long_match.length() == 1 && short_match.length()) } else if (long_match.length() == 1 && short_match.length())
{ {
throw invalid_option_format_error(opts); throw_or_mimic<invalid_option_format_error>(opts);
} }
auto option_names = [] auto option_names = []
@ -1598,7 +1698,7 @@ ParseResult::checked_parse_arg
} }
else else
{ {
throw missing_argument_exception(name); throw_or_mimic<missing_argument_exception>(name);
} }
} }
else else
@ -1623,7 +1723,7 @@ ParseResult::add_to_option(const std::string& option, const std::string& arg)
if (iter == m_options->end()) if (iter == m_options->end())
{ {
throw option_not_exists_exception(option); throw_or_mimic<option_not_exists_exception>(option);
} }
parse_option(iter->second, option, arg); parse_option(iter->second, option, arg);
@ -1659,7 +1759,10 @@ ParseResult::consume_positional(std::string a)
return true; return true;
} }
} }
++m_next_positional; else
{
throw_or_mimic<option_not_exists_exception>(*m_next_positional);
}
} }
return false; return false;
@ -1725,7 +1828,9 @@ ParseResult::parse(int& argc, char**& argv)
// but if it starts with a `-`, then it's an error // but if it starts with a `-`, then it's an error
if (argv[current][0] == '-' && argv[current][1] != '\0') { if (argv[current][0] == '-' && argv[current][1] != '\0') {
throw option_syntax_exception(argv[current]); if (!m_allow_unrecognised) {
throw_or_mimic<option_syntax_exception>(argv[current]);
}
} }
//if true is returned here then it was consumed, otherwise it is //if true is returned here then it was consumed, otherwise it is
@ -1761,7 +1866,7 @@ ParseResult::parse(int& argc, char**& argv)
else else
{ {
//error //error
throw option_not_exists_exception(name); throw_or_mimic<option_not_exists_exception>(name);
} }
} }
@ -1779,7 +1884,7 @@ ParseResult::parse(int& argc, char**& argv)
else else
{ {
//error //error
throw option_requires_argument_exception(name); throw_or_mimic<option_requires_argument_exception>(name);
} }
} }
} }
@ -1802,7 +1907,7 @@ ParseResult::parse(int& argc, char**& argv)
else else
{ {
//error //error
throw option_not_exists_exception(name); throw_or_mimic<option_not_exists_exception>(name);
} }
} }
@ -1834,7 +1939,7 @@ ParseResult::parse(int& argc, char**& argv)
auto& store = m_results[detail]; auto& store = m_results[detail];
if(!store.count() && value.has_default()){ if(value.has_default() && !store.count() && !store.has_default()){
parse_default(detail); parse_default(detail);
} }
} }
@ -1861,6 +1966,17 @@ ParseResult::parse(int& argc, char**& argv)
} }
inline
void
Options::add_option
(
const std::string& group,
const Option& option
)
{
add_options(group, {option});
}
inline inline
void void
Options::add_option Options::add_option
@ -1909,7 +2025,7 @@ Options::add_one_option
if (!in.second) if (!in.second)
{ {
throw option_exists_error(option); throw_or_mimic<option_exists_error>(option);
} }
} }
@ -1938,8 +2054,7 @@ Options::help_one_group(const std::string& g) const
for (const auto& o : group->second.options) for (const auto& o : group->second.options)
{ {
if (o.is_container && if (m_positional_set.find(o.l) != m_positional_set.end() &&
m_positional_set.find(o.l) != m_positional_set.end() &&
!m_show_positional) !m_show_positional)
{ {
continue; continue;
@ -1958,8 +2073,7 @@ Options::help_one_group(const std::string& g) const
auto fiter = format.begin(); auto fiter = format.begin();
for (const auto& o : group->second.options) for (const auto& o : group->second.options)
{ {
if (o.is_container && if (m_positional_set.find(o.l) != m_positional_set.end() &&
m_positional_set.find(o.l) != m_positional_set.end() &&
!m_show_positional) !m_show_positional)
{ {
continue; continue;

View file

@ -56,6 +56,7 @@ parse(int argc, char* argv[])
("help", "Print help") ("help", "Print help")
("int", "An integer", cxxopts::value<int>(), "N") ("int", "An integer", cxxopts::value<int>(), "N")
("float", "A floating point number", cxxopts::value<float>()) ("float", "A floating point number", cxxopts::value<float>())
("vector", "A list of doubles", cxxopts::value<std::vector<double>>())
("option_that_is_too_long_for_the_help", "A very long option") ("option_that_is_too_long_for_the_help", "A very long option")
#ifdef CXXOPTS_USE_UNICODE #ifdef CXXOPTS_USE_UNICODE
("unicode", u8"A help option with non-ascii: à. Here the size of the" ("unicode", u8"A help option with non-ascii: à. Here the size of the"
@ -130,6 +131,16 @@ parse(int argc, char* argv[])
std::cout << "float = " << result["float"].as<float>() << std::endl; std::cout << "float = " << result["float"].as<float>() << std::endl;
} }
if (result.count("vector"))
{
std::cout << "vector = ";
const auto values = result["vector"].as<std::vector<double>>();
for (const auto& v : values) {
std::cout << v << ", ";
}
std::cout << std::endl;
}
std::cout << "Arguments remain = " << argc << std::endl; std::cout << "Arguments remain = " << argc << std::endl;
return result; return result;

View file

@ -216,6 +216,22 @@ TEST_CASE("No positional with extras", "[positional]")
CHECK(argv[1] == std::string("a")); CHECK(argv[1] == std::string("a"));
} }
TEST_CASE("Positional not valid", "[positional]") {
cxxopts::Options options("positional_invalid", "invalid positional argument");
options.add_options()
("long", "a long option", cxxopts::value<std::string>())
;
options.parse_positional("something");
Argv av({"foobar", "bar", "baz"});
char** argv = av.argv();
auto argc = av.argc();
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_not_exists_exception&);
}
TEST_CASE("Empty with implicit value", "[implicit]") TEST_CASE("Empty with implicit value", "[implicit]")
{ {
cxxopts::Options options("empty_implicit", "doesn't handle empty"); cxxopts::Options options("empty_implicit", "doesn't handle empty");
@ -234,12 +250,75 @@ TEST_CASE("Empty with implicit value", "[implicit]")
REQUIRE(result["implicit"].as<std::string>() == ""); REQUIRE(result["implicit"].as<std::string>() == "");
} }
TEST_CASE("Boolean without implicit value", "[implicit]")
{
cxxopts::Options options("no_implicit", "bool without an implicit value");
options.add_options()
("bool", "Boolean without implicit", cxxopts::value<bool>()
->no_implicit_value());
SECTION("When no value provided") {
Argv av({"no_implicit", "--bool"});
char** argv = av.argv();
auto argc = av.argc();
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::missing_argument_exception&);
}
SECTION("With equal-separated true") {
Argv av({"no_implicit", "--bool=true"});
char** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
CHECK(result.count("bool") == 1);
CHECK(result["bool"].as<bool>() == true);
}
SECTION("With equal-separated false") {
Argv av({"no_implicit", "--bool=false"});
char** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
CHECK(result.count("bool") == 1);
CHECK(result["bool"].as<bool>() == false);
}
SECTION("With space-separated true") {
Argv av({"no_implicit", "--bool", "true"});
char** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
CHECK(result.count("bool") == 1);
CHECK(result["bool"].as<bool>() == true);
}
SECTION("With space-separated false") {
Argv av({"no_implicit", "--bool", "false"});
char** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
CHECK(result.count("bool") == 1);
CHECK(result["bool"].as<bool>() == false);
}
}
TEST_CASE("Default values", "[default]") TEST_CASE("Default values", "[default]")
{ {
cxxopts::Options options("defaults", "has defaults"); cxxopts::Options options("defaults", "has defaults");
options.add_options() options.add_options()
("default", "Has implicit", cxxopts::value<int>() ("default", "Has implicit", cxxopts::value<int>()->default_value("42"))
->default_value("42")); ("v,vector", "Default vector", cxxopts::value<std::vector<int>>()
->default_value("1,4"))
;
SECTION("Sets defaults") { SECTION("Sets defaults") {
Argv av({"implicit"}); Argv av({"implicit"});
@ -250,6 +329,11 @@ TEST_CASE("Default values", "[default]")
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
CHECK(result.count("default") == 0); CHECK(result.count("default") == 0);
CHECK(result["default"].as<int>() == 42); CHECK(result["default"].as<int>() == 42);
auto& v = result["vector"].as<std::vector<int>>();
REQUIRE(v.size() == 2);
CHECK(v[0] == 1);
CHECK(v[1] == 4);
} }
SECTION("When values provided") { SECTION("When values provided") {
@ -392,6 +476,8 @@ TEST_CASE("Overflow on boundary", "[integer]")
TEST_CASE("Integer overflow", "[options]") TEST_CASE("Integer overflow", "[options]")
{ {
using namespace cxxopts::values;
cxxopts::Options options("reject_overflow", "rejects overflowing integers"); cxxopts::Options options("reject_overflow", "rejects overflowing integers");
options.add_options() options.add_options()
("positional", "Integers", cxxopts::value<std::vector<int8_t>>()); ("positional", "Integers", cxxopts::value<std::vector<int8_t>>());
@ -403,6 +489,10 @@ TEST_CASE("Integer overflow", "[options]")
options.parse_positional("positional"); options.parse_positional("positional");
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::argument_incorrect_type&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::argument_incorrect_type&);
int integer = 0;
CHECK_THROWS_AS((integer_parser("23423423423", integer)), cxxopts::argument_incorrect_type&);
CHECK_THROWS_AS((integer_parser("234234234234", integer)), cxxopts::argument_incorrect_type&);
} }
TEST_CASE("Floats", "[options]") TEST_CASE("Floats", "[options]")
@ -452,6 +542,8 @@ TEST_CASE("Booleans", "[boolean]") {
("bool", "A Boolean", cxxopts::value<bool>()) ("bool", "A Boolean", cxxopts::value<bool>())
("debug", "Debugging", cxxopts::value<bool>()) ("debug", "Debugging", cxxopts::value<bool>())
("timing", "Timing", cxxopts::value<bool>()) ("timing", "Timing", cxxopts::value<bool>())
("verbose", "Verbose", cxxopts::value<bool>())
("dry-run", "Dry Run", cxxopts::value<bool>())
("noExplicitDefault", "No Explicit Default", cxxopts::value<bool>()) ("noExplicitDefault", "No Explicit Default", cxxopts::value<bool>())
("defaultTrue", "Timing", cxxopts::value<bool>()->default_value("true")) ("defaultTrue", "Timing", cxxopts::value<bool>()->default_value("true"))
("defaultFalse", "Timing", cxxopts::value<bool>()->default_value("false")) ("defaultFalse", "Timing", cxxopts::value<bool>()->default_value("false"))
@ -460,7 +552,7 @@ TEST_CASE("Booleans", "[boolean]") {
options.parse_positional("others"); options.parse_positional("others");
Argv av({"booleans", "--bool=false", "--debug=true", "--timing", "extra"}); Argv av({"booleans", "--bool=false", "--debug=true", "--timing", "--verbose=1", "--dry-run=0", "extra"});
char** argv = av.argv(); char** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
@ -470,6 +562,8 @@ TEST_CASE("Booleans", "[boolean]") {
REQUIRE(result.count("bool") == 1); REQUIRE(result.count("bool") == 1);
REQUIRE(result.count("debug") == 1); REQUIRE(result.count("debug") == 1);
REQUIRE(result.count("timing") == 1); REQUIRE(result.count("timing") == 1);
REQUIRE(result.count("verbose") == 1);
REQUIRE(result.count("dry-run") == 1);
REQUIRE(result.count("noExplicitDefault") == 0); REQUIRE(result.count("noExplicitDefault") == 0);
REQUIRE(result.count("defaultTrue") == 0); REQUIRE(result.count("defaultTrue") == 0);
REQUIRE(result.count("defaultFalse") == 0); REQUIRE(result.count("defaultFalse") == 0);
@ -477,6 +571,8 @@ TEST_CASE("Booleans", "[boolean]") {
CHECK(result["bool"].as<bool>() == false); CHECK(result["bool"].as<bool>() == false);
CHECK(result["debug"].as<bool>() == true); CHECK(result["debug"].as<bool>() == true);
CHECK(result["timing"].as<bool>() == true); CHECK(result["timing"].as<bool>() == true);
CHECK(result["verbose"].as<bool>() == true);
CHECK(result["dry-run"].as<bool>() == false);
CHECK(result["noExplicitDefault"].as<bool>() == false); CHECK(result["noExplicitDefault"].as<bool>() == false);
CHECK(result["defaultTrue"].as<bool>() == true); CHECK(result["defaultTrue"].as<bool>() == true);
CHECK(result["defaultFalse"].as<bool>() == false); CHECK(result["defaultFalse"].as<bool>() == false);
@ -484,6 +580,26 @@ TEST_CASE("Booleans", "[boolean]") {
REQUIRE(result.count("others") == 1); REQUIRE(result.count("others") == 1);
} }
TEST_CASE("std::vector", "[vector]") {
std::vector<double> vector;
cxxopts::Options options("vector", " - tests vector");
options.add_options()
("vector", "an vector option", cxxopts::value<std::vector<double>>(vector));
Argv av({"vector", "--vector", "1,-2.1,3,4.5"});
char** argv = av.argv();
auto argc = av.argc();
options.parse(argc, argv);
REQUIRE(vector.size() == 4);
CHECK(vector[0] == 1);
CHECK(vector[1] == -2.1);
CHECK(vector[2] == 3);
CHECK(vector[3] == 4.5);
}
#ifdef CXXOPTS_HAS_OPTIONAL #ifdef CXXOPTS_HAS_OPTIONAL
TEST_CASE("std::optional", "[optional]") { TEST_CASE("std::optional", "[optional]") {
std::optional<std::string> optional; std::optional<std::string> optional;
@ -533,6 +649,33 @@ TEST_CASE("Unrecognised options", "[options]") {
} }
} }
TEST_CASE("Allow bad short syntax", "[options]") {
cxxopts::Options options("unknown_options", " - test unknown options");
options.add_options()
("long", "a long option")
("s,short", "a short option");
Argv av({
"unknown_options",
"-some_bad_short",
});
char** argv = av.argv();
auto argc = av.argc();
SECTION("Default behaviour") {
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_syntax_exception&);
}
SECTION("After allowing unrecognised options") {
options.allow_unrecognised_options();
CHECK_NOTHROW(options.parse(argc, argv));
REQUIRE(argc == 2);
CHECK_THAT(argv[1], Catch::Equals("-some_bad_short"));
}
}
TEST_CASE("Invalid option syntax", "[options]") { TEST_CASE("Invalid option syntax", "[options]") {
cxxopts::Options options("invalid_syntax", " - test invalid syntax"); cxxopts::Options options("invalid_syntax", " - test invalid syntax");
@ -548,3 +691,86 @@ TEST_CASE("Invalid option syntax", "[options]") {
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_syntax_exception&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_syntax_exception&);
} }
} }
TEST_CASE("Options empty", "[options]") {
cxxopts::Options options("Options list empty", " - test empty option list");
options.add_options();
options.add_options("");
options.add_options("", {});
options.add_options("test");
Argv argv_({
"test",
"--unknown"
});
auto argc = argv_.argc();
char** argv = argv_.argv();
CHECK(options.groups().empty());
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_not_exists_exception&);
}
TEST_CASE("Initializer list with group", "[options]") {
cxxopts::Options options("Initializer list group", " - test initializer list with group");
options.add_options("", {
{"a, address", "server address", cxxopts::value<std::string>()->default_value("127.0.0.1")},
{"p, port", "server port", cxxopts::value<std::string>()->default_value("7110"), "PORT"},
});
cxxopts::Option help{"h,help", "Help"};
options.add_options("TEST_GROUP", {
{"t, test", "test option"},
help
});
Argv argv({
"test",
"--address",
"10.0.0.1",
"-p",
"8000",
"-t",
});
char** actual_argv = argv.argv();
auto argc = argv.argc();
auto result = options.parse(argc, actual_argv);
CHECK(options.groups().size() == 2);
CHECK(result.count("address") == 1);
CHECK(result.count("port") == 1);
CHECK(result.count("test") == 1);
CHECK(result.count("help") == 0);
CHECK(result["address"].as<std::string>() == "10.0.0.1");
CHECK(result["port"].as<std::string>() == "8000");
CHECK(result["test"].as<bool>() == true);
}
TEST_CASE("Option add with add_option(string, Option)", "[options]") {
cxxopts::Options options("Option add with add_option", " - test Option add with add_option(string, Option)");
cxxopts::Option option_1("t,test", "test option", cxxopts::value<int>()->default_value("7"), "TEST");
options.add_option("", option_1);
options.add_option("TEST", {"a,aggregate", "test option 2", cxxopts::value<int>(), "AGGREGATE"});
Argv argv_({
"test",
"--test",
"5",
"-a",
"4"
});
auto argc = argv_.argc();
char** argv = argv_.argv();
auto result = options.parse(argc, argv);
CHECK(result.arguments().size()==2);
CHECK(options.groups().size() == 2);
CHECK(result.count("address") == 0);
CHECK(result.count("aggregate") == 1);
CHECK(result.count("test") == 1);
CHECK(result["aggregate"].as<int>() == 4);
CHECK(result["test"].as<int>() == 5);
}