From 2ebce1048729c3030dfcfbc73c04d6a435908b5a Mon Sep 17 00:00:00 2001 From: Antonio Bellotta Date: Fri, 22 Mar 2019 16:39:17 +0100 Subject: [PATCH 01/67] First integration of CLI11 in Coreneuron --- coreneuron/engine.h | 2 +- coreneuron/nrniv/cn_parameters.cpp | 59 +++++ coreneuron/nrniv/cn_parameters.h | 61 ++++++ coreneuron/nrniv/main1.cpp | 207 +++++++++++++----- coreneuron/nrniv/nrn_setup.cpp | 10 +- tests/integration/CMakeLists.txt | 36 +-- .../test_cmdline_interface.cpp | 6 +- 7 files changed, 301 insertions(+), 80 deletions(-) create mode 100644 coreneuron/nrniv/cn_parameters.cpp create mode 100644 coreneuron/nrniv/cn_parameters.h diff --git a/coreneuron/engine.h b/coreneuron/engine.h index 415d25252..126c01a9c 100644 --- a/coreneuron/engine.h +++ b/coreneuron/engine.h @@ -5,7 +5,7 @@ extern "C" { #endif -extern void mk_mech_init(int argc, char** argv); +extern int mk_mech_init(int argc, char** argv); extern int run_solve_core(int argc, char** argv); #ifdef __cplusplus diff --git a/coreneuron/nrniv/cn_parameters.cpp b/coreneuron/nrniv/cn_parameters.cpp new file mode 100644 index 000000000..cd7445c1f --- /dev/null +++ b/coreneuron/nrniv/cn_parameters.cpp @@ -0,0 +1,59 @@ +// +// Created by bellotta on 3/22/19. +// + +#include "coreneuron/nrniv/cn_parameters.h" + +std::ostream& operator<<(std::ostream& os, const cn_parameters& cn_par){ + + os << "GENERAL PARAMETERS" << std::endl + << std::left << std::setw(15) << "MPI" << std::right << std::setw(7) << cn_par.mpi_en << " " + << std::left << std::setw(15) << "dt" << std::right << std::setw(7) << cn_par.dt << " " + << std::left << std::setw(15) << "Tstop" << std::right << std::setw(7) << cn_par.tstop << std::endl + << std::left << std::setw(15) << "Print_arg" << std::right << std::setw(7) << cn_par.print_arg << std::endl + << std::endl + << "GPU PARAMETERS" << std::endl + << std::left << std::setw(15) << "Nwarp" << std::right << std::setw(7) << cn_par.nwarp << " " + << std::left << std::setw(15) << "Cell_perm" << std::right << std::setw(7) << cn_par.cell_interleave_permute << std::endl + << std::endl + << "INPUT PARAMETERS" << std::endl + << std::left << std::setw(15) << "Voltage" << std::right << std::setw(7) << cn_par.voltage << " " + << std::left << std::setw(15) << "Seed" << std::right << std::setw(7) << cn_par.seed << std::endl + << std::left << std::setw(15) << "Datpath" << std::right << std::setw(7) << cn_par.datpath << std::endl + << std::left << std::setw(15) << "Filesdat" << std::right << std::setw(7) << cn_par.filesdat << std::endl + << std::left << std::setw(15) << "Patternstim" << std::right << std::setw(7) << cn_par.patternstim << std::endl + << std::left << std::setw(15) << "Reportpath" << std::right << std::setw(7) << cn_par.reportpath << std::endl + << std::left << std::setw(15) << "Rconfigpath" << std::right << std::setw(7) << cn_par.rconfigpath << std::endl + << std::left << std::setw(15) << "Restorepath" << std::right << std::setw(7) << cn_par.restorepath << std::endl + << std::endl + << "PARALLEL COMPUTATION PARAMETERS" << std::endl + << std::left << std::setw(15) << "Threading" << std::right << std::setw(7) << cn_par.threading << " " + << std::left << std::setw(15) << "Skip_mpi_fin" << std::right << std::setw(7) << cn_par.skip_mpi_finalize << std::endl + << std::endl + << "SPIKE EXCHANGE" << std::endl + << std::left << std::setw(15) << "Ms_phases" << std::right << std::setw(7) << cn_par.ms_phases << " " + << std::left << std::setw(15) << "Ms_Subint" << std::right << std::setw(7) << cn_par.ms_subint << " " + << std::left << std::setw(15) << "Multisend" << std::right << std::setw(7) << cn_par.multisend << std::endl + << std::left << std::setw(15) << "Spk_compress" << std::right << std::setw(7) << cn_par.spkcompress << " " + << std::left << std::setw(15) << "Binqueue" << std::right << std::setw(7) << cn_par.binqueue << std::endl + << std::endl + << "CONFIGURATION" << std::endl + << std::left << std::setw(15) << "Spike Buffer" << std::right << std::setw(7) << cn_par.spikebuf << " " + << std::left << std::setw(15) << "Pr Cell Grid" << std::right << std::setw(7) << cn_par.prcellgid << " " + << std::left << std::setw(15) << "Forwardskip" << std::right << std::setw(7) << cn_par.forwardskip << std::endl + << std::left << std::setw(15) << "Celsius" << std::right << std::setw(7) << cn_par.celsius << " " + << std::left << std::setw(15) << "Extracon" << std::right << std::setw(7) << cn_par.extracon << " " + << std::left << std::setw(15) << "Multiple" << std::right << std::setw(7) << cn_par.multiple << std::endl + << std::left << std::setw(15) << "Mindelay" << std::right << std::setw(7) << cn_par.mindelay << " " + << std::left << std::setw(15) << "Rep_buff" << std::right << std::setw(7) << cn_par.report_buff_size << std::endl + << std::endl + << "OUTPUT PARAMETERS" << std::endl + << std::left << std::setw(15) << "dt_io" << std::right << std::setw(7) << cn_par.dt_io << std::endl + << std::left << std::setw(15) << "Outpath" << std::right << std::setw(7) << cn_par.outpath << std::endl + << std::left << std::setw(15) << "Checkpointpath" << std::right << std::setw(7) << cn_par.checkpointpath<< std::endl; + + return os; +} + + +cn_parameters cn_par; \ No newline at end of file diff --git a/coreneuron/nrniv/cn_parameters.h b/coreneuron/nrniv/cn_parameters.h new file mode 100644 index 000000000..b7690dfc7 --- /dev/null +++ b/coreneuron/nrniv/cn_parameters.h @@ -0,0 +1,61 @@ +// +// Created by bellotta on 3/22/19. +// + +#ifndef CN_PARAMETERS_H +#define CN_PARAMETERS_H + +#include +#include +#include +#include + +typedef std::string string_t; + +struct cn_parameters { + + int spikebuf=100000; /// internal buffer used on every rank for spikes + int prcellgid; /// gid of cell for prcellstate + int ms_phases=2; + int ms_subint=2; + int spkcompress=0; + int cell_interleave_permute=1; /// cell interleaving permutation + int nwarp=0; /// number of warps to balance for cell_interleave_permute == 2 + int multiple; + int extracon; + int report_buff_size; + int seed=0; /// Initialization seed for random number generator (int) + + bool mpi_en; /// Enable MPI flag. + bool print_arg; /// Print arguments flag. + bool skip_mpi_finalize; /// Skip MPI finalization + bool multisend; + bool threading; /// enable pthread/openmp + bool gpu; + bool binqueue; + + double tstop=100; /// stop time of simulation in msec + double dt=0.025; /// timestep to use in msec + double dt_io=0.1; /// i/o timestep to use in msec + double dt_report; /// i/o timestep to use in msec for reports + double celsius=34.0; + double voltage=-65.0; + double forwardskip; + double mindelay=10.; + + string_t patternstim; /// Apply patternstim using the specified spike file. + string_t datpath="."; /// directory path where .dat files + string_t outpath="."; /// directory where spikes will be written + string_t filesdat="files.dat"; /// name of file containing list of gids dat files read in + string_t rconfigpath; + string_t restorepath; + string_t reportpath; + string_t checkpointpath; + +}; + +std::ostream& operator<<(std::ostream& os, const cn_parameters& cn_par); + +extern cn_parameters cn_par; + +#endif //CN_PARAMETERS_H diff --git a/coreneuron/nrniv/main1.cpp b/coreneuron/nrniv/main1.cpp index 85e9dac0a..0149f2558 100644 --- a/coreneuron/nrniv/main1.cpp +++ b/coreneuron/nrniv/main1.cpp @@ -56,6 +56,8 @@ THE POSSIBILITY OF SUCH DAMAGE. #include "coreneuron/nrniv/multisend.h" #include "coreneuron/utils/file_utils.h" #include "coreneuron/nrniv/nrn2core_direct.h" +#include "coreneuron/nrniv/cn_parameters.h" +#include "coreneuron/utils/CLI11/CLI.hpp" #include #include @@ -182,10 +184,10 @@ void nrn_init_and_load_data(int argc, // set global variables // precedence is: set by user, globals.dat, 34.0 - celsius = nrnopt_get_dbl("--celsius"); + celsius = cn_par.celsius; #if _OPENACC - if (!nrnopt_get_flag("--gpu") && nrnopt_get_int("--cell-permute") == 2) { + if (!cn_par.gpu && cn_par.cell_interleave_permute == 2) { fprintf( stderr, "compiled with _OPENACC does not allow the combination of --cell-permute=2 and missing --gpu\n"); @@ -195,37 +197,39 @@ void nrn_init_and_load_data(int argc, // if multi-threading enabled, make sure mpi library supports it #if NRNMPI - if (nrnopt_get_flag("--threading")) { + if (cn_par.threading) { nrnmpi_check_threading_support(); } #endif // full path of files.dat file - std::string filesdat(nrnopt_get_str("--datpath") + "/" + nrnopt_get_str("--filesdat")); + std::string filesdat(cn_par.datpath + "/" + cn_par.filesdat); // read the global variable names and set their values from globals.dat - set_globals(nrnopt_get_str("--datpath").c_str(), nrnopt_get_flag("--seed"), - nrnopt_get_int("--seed")); + set_globals(cn_par.datpath.c_str(), (cn_par.seed==0), + cn_par.seed); // set global variables for start time, timestep and temperature - std::string restore_path = nrnopt_get_str("--restore"); + std::string restore_path = cn_par.reportpath; t = restore_time(restore_path.c_str()); - if (nrnopt_get_dbl("--dt") != -1000.) { // command line arg highest precedence - dt = nrnopt_get_dbl("--dt"); + if (cn_par.dt != -1000.) { // command line arg highest precedence + dt = cn_par.dt; } else if (dt == -1000.) { // not on command line and no dt in globals.dat dt = 0.025; // lowest precedence } - nrnopt_modify_dbl("--dt", dt); + + cn_par.dt = dt; rev_dt = (int)(1. / dt); - if (nrnopt_get_dbl("--celsius") != -1000.) { // command line arg highest precedence - celsius = nrnopt_get_dbl("--celsius"); + if (cn_par.celsius != -1000.) { // command line arg highest precedence + celsius = cn_par.celsius; } else if (celsius == -1000.) { // not on command line and no celsius in globals.dat celsius = 34.0; // lowest precedence } - nrnopt_modify_dbl("--celsius", celsius); + + cn_par.celsius = celsius; #ifdef ISPC_INTEROP ispc_celsius = celsius; @@ -234,16 +238,16 @@ void nrn_init_and_load_data(int argc, mk_netcvode(); // One part done before call to nrn_setup. Other part after. - if (nrnopt_get_flag("--pattern")) { + if (cn_par.patternstim.empty()) { nrn_set_extra_thread0_vdata(); } report_mem_usage("Before nrn_setup"); // set if need to interleave cells - use_interleave_permute = nrnopt_get_int("--cell-permute"); - cellorder_nwarp = nrnopt_get_int("--nwarp"); - use_solve_interleave = nrnopt_get_int("--cell-permute"); + use_interleave_permute = cn_par.cell_interleave_permute; + cellorder_nwarp = cn_par.nwarp; + use_solve_interleave = cn_par.cell_interleave_permute; #if LAYOUT == 1 // permuting not allowed for AoS @@ -251,7 +255,7 @@ void nrn_init_and_load_data(int argc, use_solve_interleave = 0; #endif - if (nrnopt_get_flag("--gpu") && use_interleave_permute == 0) { + if (cn_par.gpu && use_interleave_permute == 0) { if (nrnmpi_myid == 0) { printf( " WARNING : GPU execution requires --cell-permute type 1 or 2. Setting it to 1.\n"); @@ -261,26 +265,26 @@ void nrn_init_and_load_data(int argc, } // pass by flag so existing tests do not need a changed nrn_setup prototype. - nrn_setup_multiple = nrnopt_get_int("--multiple"); - nrn_setup_extracon = nrnopt_get_int("--extracon"); + nrn_setup_multiple = cn_par.multiple; + nrn_setup_extracon = cn_par.extracon; // multisend options - use_multisend_ = nrnopt_get_flag("--multisend") ? 1 : 0; - n_multisend_interval = nrnopt_get_int("--ms-subintervals"); - use_phase2_ = (nrnopt_get_int("--ms-phases") == 2) ? 1 : 0; + use_multisend_ = cn_par.multisend ? 1 : 0; + n_multisend_interval = cn_par.multisend; + use_phase2_ = (cn_par.ms_phases == 2) ? 1 : 0; // reading *.dat files and setting up the data structures, setting mindelay nrn_setup(filesdat.c_str(), is_mapping_needed, nrn_need_byteswap, run_setup_cleanup); // Allgather spike compression and bin queuing. - nrn_use_bin_queue_ = nrnopt_get_flag("--binqueue"); - int spkcompress = nrnopt_get_int("--spkcompress"); + nrn_use_bin_queue_ = cn_par.binqueue; + int spkcompress = cn_par.spkcompress; nrnmpi_spike_compress(spkcompress, (spkcompress ? true : false), use_multisend_); report_mem_usage("After nrn_setup "); // Invoke PatternStim - if (nrnopt_get_flag("--pattern")) { - nrn_mkPatternStim(nrnopt_get_str("--pattern").c_str()); + if (cn_par.patternstim.empty()) { + nrn_mkPatternStim(cn_par.patternstim.c_str()); } /// Setting the timeout @@ -293,11 +297,11 @@ void nrn_init_and_load_data(int argc, } // allocate buffer for mpi communication - mk_spikevec_buffer(nrnopt_get_int("--spikebuf")); + mk_spikevec_buffer(cn_par.spikebuf); report_mem_usage("After mk_spikevec_buffer"); - if (nrnopt_get_flag("-gpu")) { + if (cn_par.gpu) { setup_nrnthreads_on_device(nrn_threads, nrn_nthread); } @@ -306,7 +310,7 @@ void nrn_init_and_load_data(int argc, } // call prcellstate for prcellgid - call_prcellstate_for_prcellgid(nrnopt_get_int("--prcellgid"), nrnopt_get_flag("-gpu"), 1); + call_prcellstate_for_prcellgid(cn_par.prcellgid, cn_par.gpu, 1); } void call_prcellstate_for_prcellgid(int prcellgid, int compute_gpu, int is_init) { @@ -368,15 +372,106 @@ const char* nrn_version(int) { using namespace coreneuron; + extern "C" void mk_mech_init(int argc, char** argv) { -#if NRNMPI - nrnmpi_init(1, &argc, &argv); -#endif + #if NRNMPI + nrnmpi_init(1, &argc, &argv); + #endif // read command line parameters and parameter config files - nrnopt_parse(argc, (const char**)argv); + + CLI::App app{"CoreNeuron - Your friendly neuron simulator."}; + + app.get_formatter()->column_width(50); + app.set_help_all_flag("-H, --help-all", "Print this help including subcommands and exit."); + + app.set_config("--config", "config.ini", "Read parameters from ini file", false) + ->check(CLI::ExistingFile); + app.add_flag("--mpi", cn_par.mpi_en, "Enable MPI. In order to initialize MPI environment this argument must be specified." ); + app.add_flag("--gpu", cn_par.gpu, "Activate GPU computation."); + app.add_option("--dt", cn_par.dt, "Fixed time step. The default value is set by defaults.dat or is 0.025.", true) + ->check(CLI::Range(-1000.,1e9)); + app.add_option("-e, --tstop", cn_par.tstop, "Stop Time in ms.") + ->check(CLI::Range(0., 1e9)); + app.add_flag("--show", cn_par.print_arg, "Print arguments."); + + auto sub_gpu = app.add_subcommand("gpu", "Commands relative to GPU."); + sub_gpu -> add_option("-W, --nwarp", cn_par.nwarp, "Number of warps to balance.", true) + ->check(CLI::Range(0, 1000000)); + sub_gpu -> add_option("-R, --cell-permute", cn_par.cell_interleave_permute, "Cell permutation: 0 No permutation; 1 optimise node adjacency; 2 optimize parent adjacency.", true) + ->check(CLI::Range(0, 3)); + + auto sub_input = app.add_subcommand("input", "Input dataset options."); + sub_input -> add_option("-d, --datapath", cn_par.datpath, "Path containing CoreNeuron data files.") + ->check(CLI::ExistingPath); + sub_input -> add_option("-f, --filesdat", cn_par.filesdat, "Name for the distribution file.", true) + ->check(CLI::ExistingFile); + sub_input -> add_option("-p, --pattern", cn_par.patternstim, "Apply patternstim using the specified spike file.") + ->check(CLI::ExistingFile); + sub_input -> add_option("-s, --seed", cn_par.seed, "Initialization seed for random number generator.") + ->check(CLI::Range(0, 100000000)); + sub_input -> add_option("-v, --voltage", cn_par.voltage, "Initial voltage used for nrn_finitialize(1, v_init). If 1000, then nrn_finitialize(0,...).") + ->check(CLI::Range(-1e9, 1e9)); + sub_input -> add_option("--read-config", cn_par.rconfigpath, "Read configuration file filename.") + ->check(CLI::ExistingPath); + sub_input -> add_option("--report-conf", cn_par.reportpath, "Reports configuration file.") + ->check(CLI::ExistingPath); + sub_input -> add_option("--restore", cn_par.restorepath, "Restore simulation from provided checkpoint directory.") + ->check(CLI::ExistingPath); + + auto sub_parallel = app.add_subcommand("parallel", "Parallel processing options."); + sub_parallel -> add_flag("-c, --threading", cn_par.threading, "Parallel threads. The default is serial threads."); + sub_parallel -> add_flag("--skip-mpi-finalize", cn_par.skip_mpi_finalize, "Do not call mpi finalize."); + + auto sub_spike = app.add_subcommand("spike", "Spike exchange options."); + sub_spike -> add_option("--ms-phases", cn_par.ms_phases, "Number of multisend phases, 1 or 2.", true) + ->check(CLI::Range(1, 2)); + sub_spike -> add_option("--ms-subintervals", cn_par.ms_subint, "Number of multisend subintervals, 1 or 2.", true) + ->check(CLI::Range(1, 2)); + sub_spike -> add_flag("--multisend", cn_par.multisend, "Use Multisend spike exchange instead of Allgather."); + sub_spike -> add_option("--spkcompress", cn_par.spkcompress, "Spike compression. Up to ARG are exchanged during MPI_Allgather.", true) + ->check(CLI::Range(0, 100000)); + sub_spike->add_flag("--binqueue", cn_par.binqueue, "Use bin queue." ); + + auto sub_config = app.add_subcommand("config", "Config options."); + sub_config -> add_option("-b, --spikebuf", cn_par.spikebuf, "Spike buffer size.", true) + ->check(CLI::Range(0, 2000000000)); + sub_config -> add_option("-g, --prcellgid", cn_par.prcellgid, "Output prcellstate information for the gid NUMBER.") + ->check(CLI::Range(0, 2000000000)); + sub_config -> add_option("-k, --forwardskip", cn_par.forwardskip, "Forwardskip to TIME") + ->check(CLI::Range(0., 1e9)); + sub_config -> add_option("-l, --celsius", cn_par.celsius, "Temperature in degC. The default value is set in defaults.dat or else is 34.0.", true) + ->check(CLI::Range(-1000., 1000.)); + sub_config -> add_option("-x, --extracon", cn_par.extracon, "Number of extra random connections in each thread to other duplicate models.") + ->check(CLI::Range(0, 10000000)); + sub_config -> add_option("-z, --multiple", cn_par.multiple, "Model duplication factor. Model size is normal size * multiple") + ->check(CLI::Range(1, 10000000)); + sub_config -> add_option("--mindelay", cn_par.mindelay, "Maximum integration interval (likely reduced by minimum NetCon delay).", true) + ->check(CLI::Range(0., 1e9)); + sub_config -> add_option("--report-buffer-size", cn_par.report_buff_size, "Size in MB of the report buffer.") + ->check(CLI::Range(1, 128)); + + auto sub_output = app.add_subcommand("output", "Output configuration."); + sub_output -> add_option("-i, --dt_io", cn_par.dt_io, "Dt of I/O.", true) + ->check(CLI::Range(-1000., 1e9)); + sub_output -> add_option("-o, --outpath", cn_par.outpath, "Path to place output data files.", true) + ->check(CLI::ExistingPath); + sub_output -> add_option("--checkpoint", cn_par.checkpointpath, "Enable checkpoint and specify directory to store related files.") + ->check(CLI::ExistingDirectory); + + CLI11_PARSE(app, argc, argv); + + if (cn_par.print_arg) { + std::cout << std::fixed << std::setprecision(2); + std::cout << cn_par << std::endl; + } + + std::ofstream out("last_config.ini"); + out << app.config_to_str(true, true); + out.close(); // reads mechanism information from bbcore_mech.dat - mk_mech(nrnopt_get_str("--datpath").c_str()); + + mk_mech((cn_par.datpath).c_str()); } extern "C" int run_solve_core(int argc, char** argv) { @@ -387,27 +482,28 @@ extern "C" int run_solve_core(int argc, char** argv) { report_mem_usage("After mk_mech"); - if (nrnopt_get_str("--report-conf").size()) { - if (nrnopt_get_int("--multiple") > 1) { + if ((cn_par.reportpath).empty()) { + if (cn_par.multiple > 1) { if (nrnmpi_myid == 0) printf("\n WARNING! : Can't enable reports with model duplications feature! \n"); } else { - configs = create_report_configurations(nrnopt_get_str("--report-conf").c_str(), - nrnopt_get_str("--outpath").c_str()); + configs = create_report_configurations(cn_par.reportpath.c_str(), + cn_par.outpath.c_str()); reports_needs_finalize = configs.size(); } } - // initializationa and loading functions moved to separate + // initialization and loading functions moved to separate Instrumentor::phase_begin("load-model"); - nrn_init_and_load_data(argc, argv, !configs.empty()); + nrn_init_and_load_data(argc, argv, configs.size() > 0); Instrumentor::phase_end("load-model"); - std::string checkpoint_path = nrnopt_get_str("--checkpoint"); + std::string checkpoint_path = cn_par.checkpointpath; + if (strlen(checkpoint_path.c_str())) { nrn_checkpoint_arg_exists = true; } - std::string output_dir = nrnopt_get_str("--outpath"); + std::string output_dir = cn_par.outpath; if (nrnmpi_myid == 0) { mkdir_p(output_dir.c_str()); @@ -415,14 +511,14 @@ extern "C" int run_solve_core(int argc, char** argv) { #if NRNMPI nrnmpi_barrier(); #endif - bool compute_gpu = nrnopt_get_flag("-gpu"); - bool skip_mpi_finalize = nrnopt_get_flag("--skip-mpi-finalize"); + bool compute_gpu = cn_par.gpu; + bool skip_mpi_finalize = cn_par.skip_mpi_finalize; // clang-format off #pragma acc data copyin(celsius, secondorder) if (compute_gpu) // clang-format on { - double v = nrnopt_get_dbl("--voltage"); + double v = cn_par.voltage; // TODO : if some ranks are empty then restore will go in deadlock // phase (as some ranks won't have restored anything and hence return @@ -432,9 +528,9 @@ extern "C" int run_solve_core(int argc, char** argv) { } report_mem_usage("After nrn_finitialize"); - double dt = nrnopt_get_dbl("--dt"); - double delay = nrnopt_get_dbl("--mindelay"); - double tstop = nrnopt_get_dbl("--tstop"); + double dt = cn_par.dt; + double delay = cn_par.mindelay; + double tstop = cn_par.tstop; if (tstop < t && nrnmpi_myid == 0) { printf("Error: Stop time (%lf) < Start time (%lf), restoring from checkpoint? \n", @@ -444,7 +540,7 @@ extern "C" int run_solve_core(int argc, char** argv) { // register all reports into reportinglib double min_report_dt = INT_MAX; - int report_buffer_size = nrnopt_get_int("--report-buffer-size"); + int report_buffer_size = cn_par.report_buff_size; for (size_t i = 0; i < configs.size(); i++) { register_report(dt, tstop, delay, configs[i]); if (configs[i].report_dt < min_report_dt) { @@ -460,25 +556,26 @@ extern "C" int run_solve_core(int argc, char** argv) { configs.clear(); // call prcellstate for prcellgid - call_prcellstate_for_prcellgid(nrnopt_get_int("--prcellgid"), compute_gpu, 0); + call_prcellstate_for_prcellgid(cn_par.prcellgid, compute_gpu, 0); // handle forwardskip - if (nrnopt_get_dbl("--forwardskip") > 0.0) { - handle_forward_skip(nrnopt_get_dbl("--forwardskip"), nrnopt_get_int("--prcellgid")); + if (cn_par.forwardskip > 0.0) { + handle_forward_skip(cn_par.forwardskip, cn_par.prcellgid); } /// Solver execution Instrumentor::start_profile(); Instrumentor::phase_begin("simulation"); - BBS_netpar_solve(nrnopt_get_dbl("--tstop")); + BBS_netpar_solve(cn_par.tstop); Instrumentor::phase_end("simulation"); Instrumentor::stop_profile(); + // Report global cell statistics report_cell_stats(); // prcellstate after end of solver - call_prcellstate_for_prcellgid(nrnopt_get_int("--prcellgid"), compute_gpu, 0); + call_prcellstate_for_prcellgid(cn_par.prcellgid, compute_gpu, 0); } // write spike information to outpath diff --git a/coreneuron/nrniv/nrn_setup.cpp b/coreneuron/nrniv/nrn_setup.cpp index 7729035a0..2f0ce79d7 100644 --- a/coreneuron/nrniv/nrn_setup.cpp +++ b/coreneuron/nrniv/nrn_setup.cpp @@ -48,6 +48,7 @@ THE POSSIBILITY OF SUCH DAMAGE. #include "coreneuron/nrniv/node_permute.h" #include "coreneuron/nrniv/cellorder.h" #include "coreneuron/utils/reports/nrnsection_mapping.h" +#include "coreneuron/nrniv/cn_parameters.h" // callbacks into nrn/src/nrniv/nrnbbcore_write.cpp #include "coreneuron/nrniv/nrn2core_direct.h" @@ -724,8 +725,8 @@ void nrn_setup(const char* filesdat, FileHandler* file_reader = new FileHandler[ngroup]; - std::string datapath = nrnopt_get_str("--datpath"); - std::string restore_path = nrnopt_get_str("--restore"); + std::string datapath = cn_par.datpath; + std::string restore_path = cn_par.restorepath; // if not restoring then phase2 files will be read from dataset directory if (!restore_path.length()) { @@ -774,8 +775,9 @@ void nrn_setup(const char* filesdat, if (is_mapping_needed) coreneuron::phase_wrapper<(coreneuron::phase)3>(); - double mindelay = set_mindelay(nrnopt_get_dbl("--mindelay")); - nrnopt_modify_dbl("--mindelay", mindelay); + double mindelay = set_mindelay(cn_par.mindelay); + + cn_par.mindelay = mindelay; if (run_setup_cleanup) // if run_setup_cleanup==false, user must call nrn_setup_cleanup() later nrn_setup_cleanup(); diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt index 8a9bbd849..cba586bd9 100644 --- a/tests/integration/CMakeLists.txt +++ b/tests/integration/CMakeLists.txt @@ -25,7 +25,7 @@ # THE POSSIBILITY OF SUCH DAMAGE. -set(COMMON_ARGS "--tstop 100. --celsius 6.3 -mpi") +set(COMMON_ARGS "--mpi --tstop 100. config --celsius 6.3") set(RING_DATASET_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ring") set(RING_COMMON_ARGS "--datpath ${RING_DATASET_DIR} ${COMMON_ARGS}") set(RING_GAP_COMMON_ARGS "--datpath ${CMAKE_CURRENT_SOURCE_DIR}/ring_gap ${COMMON_ARGS}") @@ -37,20 +37,20 @@ endif() # List of tests with arguments set(TEST_CASE_ARGS - "ring!${RING_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring" - "ring_binqueue!${RING_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_binqueue --binqueue" - "ring_multisend!${RING_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_multisend --multisend" - "ring_spike_buffer!${RING_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_spike_buffer -b 1" - "ring_duplicate!${RING_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_duplicate -z 3" - "ring_permute1!${RING_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_permute1 ${PERMUTE1_ARGS}" - "ring_permute2!${RING_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_permute2 ${PERMUTE2_ARGS}" - "ring_gap!${RING_GAP_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_gap" - "ring_gap_binqueue!${RING_GAP_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_gap_binqueue --binqueue" - "ring_gap_multisend!${RING_GAP_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_gap_multisend --multisend" - "ring_gap_permute1!${RING_GAP_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_gap_permute1 ${PERMUTE1_ARGS}" - "ring_gap_permute2!${RING_GAP_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_gap_permute2 ${PERMUTE2_ARGS}") - -set(NEGATIVE_TEST_CASE_ARGS "ring_different_seed!${RING_COMMON_ARGS} ${GPU_ARGS} --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_different_seed --seed 123456") + "ring!${RING_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring" + "ring_binqueue!${RING_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_binqueue --binqueue" + "ring_multisend!${RING_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_multisend --multisend" + "ring_spike_buffer!${RING_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_spike_buffer -b 1" + "ring_duplicate!${RING_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_duplicate -z 3" + "ring_permute1!${RING_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_permute1 ${PERMUTE1_ARGS}" + "ring_permute2!${RING_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_permute2 ${PERMUTE2_ARGS}" + "ring_gap!${RING_GAP_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_gap" + "ring_gap_binqueue!${RING_GAP_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_gap_binqueue --binqueue" + "ring_gap_multisend!${RING_GAP_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_gap_multisend --multisend" + "ring_gap_permute1!${RING_GAP_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_gap_permute1 ${PERMUTE1_ARGS}" + "ring_gap_permute2!${RING_GAP_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_gap_permute2 ${PERMUTE2_ARGS}") + +set(NEGATIVE_TEST_CASE_ARGS "ring_different_seed!${RING_COMMON_ARGS} ${GPU_ARGS} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/ring_different_seed --seed 123456") # There are no directories for permute and multisend related tests, create them and copy ref spikes foreach(data_dir "ring" "ring_gap") @@ -105,11 +105,11 @@ if(MPI_FOUND) endif() set(SIM_NAME "${TEST_NAME}/${SUBTEST_NAME}") if(${SUBTEST_NAME} MATCHES "generic") - set(TEST_ARGS "--datpath ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME} --outpath ${CMAKE_CURRENT_BINARY_DIR}/${SIM_NAME} --tstop 100. -mpi") + set(TEST_ARGS "--datpath ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/${SIM_NAME} --tstop 100. -mpi") elseif(${SUBTEST_NAME} MATCHES "forwardskip") - set(TEST_ARGS "--datpath=${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME} --outpath=${CMAKE_CURRENT_BINARY_DIR}/${SIM_NAME} --forwardskip 5000. --tstop 100. -mpi") + set(TEST_ARGS "--datpath=${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME} output --outpath=${CMAKE_CURRENT_BINARY_DIR}/${SIM_NAME} --forwardskip 5000. --tstop 100. -mpi") elseif(${SUBTEST_NAME} MATCHES "spikereplay") - set(TEST_ARGS "--datpath ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME} --outpath ${CMAKE_CURRENT_BINARY_DIR}/${SIM_NAME} --pattern ${CMAKE_CURRENT_SOURCE_DIR}/${SIM_NAME}/out.std --tstop 100. -mpi") + set(TEST_ARGS "--datpath ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME} output --outpath ${CMAKE_CURRENT_BINARY_DIR}/${SIM_NAME} --pattern ${CMAKE_CURRENT_SOURCE_DIR}/${SIM_NAME}/out.std --tstop 100. -mpi") endif() set(TEST_ARGS "${TEST_ARGS} ${GPU_ARGS}") diff --git a/tests/unit/cmdline_interface/test_cmdline_interface.cpp b/tests/unit/cmdline_interface/test_cmdline_interface.cpp index 1819b9201..ae3ba26be 100644 --- a/tests/unit/cmdline_interface/test_cmdline_interface.cpp +++ b/tests/unit/cmdline_interface/test_cmdline_interface.cpp @@ -32,6 +32,8 @@ THE POSSIBILITY OF SUCH DAMAGE. #include #include "nrniv/nrnoptarg.h" #include +#include "coreneuron/nrniv/cn_parameters.h" + using namespace coreneuron; BOOST_AUTO_TEST_CASE(cmdline_interface) { @@ -124,8 +126,8 @@ BOOST_AUTO_TEST_CASE(cmdline_interface) { nrnopt_parse(argc, argv); - BOOST_CHECK(nrnopt_get_int("--seed") == -1); // testing default value - + BOOST_CHECK(cn_par.seed == -1); // testing default value + BOOST_CHECK(nrnopt_get_int("--spikebuf") == 100); BOOST_CHECK(nrnopt_get_flag("--threading") == true); From 366bbc02fd5f64cf210f10673944e8cbe52cf597 Mon Sep 17 00:00:00 2001 From: Antonio Bellotta Date: Mon, 25 Mar 2019 11:24:30 +0100 Subject: [PATCH 02/67] CLI11 integration completed. --- coreneuron/nrniv/cn_parameters.cpp | 2 +- coreneuron/nrniv/cn_parameters.h | 26 +- coreneuron/nrniv/main1.cpp | 13 +- coreneuron/utils/CLI11/CLI.hpp | 4641 ++++++++++++++++++++++++++++ tests/integration/CMakeLists.txt | 16 +- 5 files changed, 4670 insertions(+), 28 deletions(-) create mode 100644 coreneuron/utils/CLI11/CLI.hpp diff --git a/coreneuron/nrniv/cn_parameters.cpp b/coreneuron/nrniv/cn_parameters.cpp index cd7445c1f..ec9c1cfc5 100644 --- a/coreneuron/nrniv/cn_parameters.cpp +++ b/coreneuron/nrniv/cn_parameters.cpp @@ -39,7 +39,7 @@ std::ostream& operator<<(std::ostream& os, const cn_parameters& cn_par){ << std::endl << "CONFIGURATION" << std::endl << std::left << std::setw(15) << "Spike Buffer" << std::right << std::setw(7) << cn_par.spikebuf << " " - << std::left << std::setw(15) << "Pr Cell Grid" << std::right << std::setw(7) << cn_par.prcellgid << " " + << std::left << std::setw(15) << "Pr Cell Gid" << std::right << std::setw(7) << cn_par.prcellgid << " " << std::left << std::setw(15) << "Forwardskip" << std::right << std::setw(7) << cn_par.forwardskip << std::endl << std::left << std::setw(15) << "Celsius" << std::right << std::setw(7) << cn_par.celsius << " " << std::left << std::setw(15) << "Extracon" << std::right << std::setw(7) << cn_par.extracon << " " diff --git a/coreneuron/nrniv/cn_parameters.h b/coreneuron/nrniv/cn_parameters.h index b7690dfc7..8b4d9c954 100644 --- a/coreneuron/nrniv/cn_parameters.h +++ b/coreneuron/nrniv/cn_parameters.h @@ -15,24 +15,24 @@ typedef std::string string_t; struct cn_parameters { int spikebuf=100000; /// internal buffer used on every rank for spikes - int prcellgid; /// gid of cell for prcellstate + int prcellgid=-1; /// gid of cell for prcellstate int ms_phases=2; int ms_subint=2; int spkcompress=0; - int cell_interleave_permute=1; /// cell interleaving permutation + int cell_interleave_permute=0; /// cell interleaving permutation int nwarp=0; /// number of warps to balance for cell_interleave_permute == 2 - int multiple; - int extracon; - int report_buff_size; + int multiple=1; + int extracon=0; + int report_buff_size=4; int seed=0; /// Initialization seed for random number generator (int) - bool mpi_en; /// Enable MPI flag. - bool print_arg; /// Print arguments flag. - bool skip_mpi_finalize; /// Skip MPI finalization - bool multisend; - bool threading; /// enable pthread/openmp - bool gpu; - bool binqueue; + bool mpi_en=0; /// Enable MPI flag. + bool print_arg=0; /// Print arguments flag. + bool skip_mpi_finalize=0; /// Skip MPI finalization + bool multisend=0; + bool threading=0; /// enable pthread/openmp + bool gpu=0; + bool binqueue=0; double tstop=100; /// stop time of simulation in msec double dt=0.025; /// timestep to use in msec @@ -40,7 +40,7 @@ struct cn_parameters { double dt_report; /// i/o timestep to use in msec for reports double celsius=34.0; double voltage=-65.0; - double forwardskip; + double forwardskip=0.; double mindelay=10.; string_t patternstim; /// Apply patternstim using the specified spike file. diff --git a/coreneuron/nrniv/main1.cpp b/coreneuron/nrniv/main1.cpp index 0149f2558..103bc3f0f 100644 --- a/coreneuron/nrniv/main1.cpp +++ b/coreneuron/nrniv/main1.cpp @@ -238,7 +238,8 @@ void nrn_init_and_load_data(int argc, mk_netcvode(); // One part done before call to nrn_setup. Other part after. - if (cn_par.patternstim.empty()) { + + if (!cn_par.patternstim.empty()) { nrn_set_extra_thread0_vdata(); } @@ -283,7 +284,7 @@ void nrn_init_and_load_data(int argc, report_mem_usage("After nrn_setup "); // Invoke PatternStim - if (cn_par.patternstim.empty()) { + if (!cn_par.patternstim.empty()) { nrn_mkPatternStim(cn_par.patternstim.c_str()); } @@ -291,7 +292,7 @@ void nrn_init_and_load_data(int argc, nrn_set_timeout(200.); // show all configuration parameters for current run - nrnopt_show(); + //nrnopt_show(); if (nrnmpi_myid == 0) { std::cout << " Start time (t) = " << t << std::endl << std::endl; } @@ -401,7 +402,7 @@ extern "C" void mk_mech_init(int argc, char** argv) { ->check(CLI::Range(0, 3)); auto sub_input = app.add_subcommand("input", "Input dataset options."); - sub_input -> add_option("-d, --datapath", cn_par.datpath, "Path containing CoreNeuron data files.") + sub_input -> add_option("-d, --datpath", cn_par.datpath, "Path containing CoreNeuron data files.") ->check(CLI::ExistingPath); sub_input -> add_option("-f, --filesdat", cn_par.filesdat, "Name for the distribution file.", true) ->check(CLI::ExistingFile); @@ -482,7 +483,7 @@ extern "C" int run_solve_core(int argc, char** argv) { report_mem_usage("After mk_mech"); - if ((cn_par.reportpath).empty()) { + if (cn_par.reportpath.size()) { if (cn_par.multiple > 1) { if (nrnmpi_myid == 0) printf("\n WARNING! : Can't enable reports with model duplications feature! \n"); @@ -549,7 +550,7 @@ extern "C" int run_solve_core(int argc, char** argv) { } // Set the buffer size if is not the default value. Otherwise use report.conf on // register_report - if (!nrnopt_is_default_value("--report-buffer-size")) { + if (cn_par.report_buff_size!=4) { set_report_buffer_size(report_buffer_size); } setup_report_engine(min_report_dt, delay); diff --git a/coreneuron/utils/CLI11/CLI.hpp b/coreneuron/utils/CLI11/CLI.hpp new file mode 100644 index 000000000..ae58c8152 --- /dev/null +++ b/coreneuron/utils/CLI11/CLI.hpp @@ -0,0 +1,4641 @@ +#pragma once + +// CLI11: Version 1.7.1 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v1.7.1 +// +// From LICENSE: +// +// CLI11 1.7 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Standard combined includes: + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Verbatim copy from CLI/Version.hpp: + + +#define CLI11_VERSION_MAJOR 1 +#define CLI11_VERSION_MINOR 7 +#define CLI11_VERSION_PATCH 1 +#define CLI11_VERSION "1.7.1" + + + + +// Verbatim copy from CLI/Macros.hpp: + + +// The following version macro is very similar to the one in PyBind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if __MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + + + + +// Verbatim copy from CLI/Optional.hpp: + +#ifdef __has_include + +// You can explicitly enable or disable support +// by defining these to 1 or 0. +#if defined(CLI11_CPP17) && __has_include() && \ + !defined(CLI11_STD_OPTIONAL) +#define CLI11_STD_OPTIONAL 1 +#elif !defined(CLI11_STD_OPTIONAL) +#define CLI11_STD_OPTIONAL 0 +#endif + +#if defined(CLI11_CPP14) && __has_include() && \ + !defined(CLI11_EXPERIMENTAL_OPTIONAL) \ + && (!defined(CLI11_STD_OPTIONAL) || CLI11_STD_OPTIONAL == 0) +#define CLI11_EXPERIMENTAL_OPTIONAL 1 +#elif !defined(CLI11_EXPERIMENTAL_OPTIONAL) +#define CLI11_EXPERIMENTAL_OPTIONAL 0 +#endif + +#if __has_include() && !defined(CLI11_BOOST_OPTIONAL) +#include +#if BOOST_VERSION >= 105800 +#define CLI11_BOOST_OPTIONAL 1 +#endif +#elif !defined(CLI11_BOOST_OPTIONAL) +#define CLI11_BOOST_OPTIONAL 0 +#endif + +#endif + +#if CLI11_STD_OPTIONAL +#include +#endif +#if CLI11_EXPERIMENTAL_OPTIONAL +#include +#endif +#if CLI11_BOOST_OPTIONAL +#include +#endif + + +// From CLI/Version.hpp: + + + +// From CLI/Macros.hpp: + + + +// From CLI/Optional.hpp: + +namespace CLI { + +#if CLI11_STD_OPTIONAL +template std::istream &operator>>(std::istream &in, std::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#if CLI11_EXPERIMENTAL_OPTIONAL +template std::istream &operator>>(std::istream &in, std::experimental::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#if CLI11_BOOST_OPTIONAL +template std::istream &operator>>(std::istream &in, boost::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +// Export the best optional to the CLI namespace +#if CLI11_STD_OPTIONAL +using std::optional; +#elif CLI11_EXPERIMENTAL_OPTIONAL +using std::experimental::optional; +#elif CLI11_BOOST_OPTIONAL +using boost::optional; +#endif + +// This is true if any optional is found +#if CLI11_STD_OPTIONAL || CLI11_EXPERIMENTAL_OPTIONAL || CLI11_BOOST_OPTIONAL +#define CLI11_OPTIONAL 1 +#endif + +} // namespace CLI + +// From CLI/StringTools.hpp: + +namespace CLI { +namespace detail { + +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +inline std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) + elems.emplace_back(""); + else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + size_t start = 0; + for(const auto &i : v) { + if(start++ > 0) + s << delim; + s << i; + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +inline std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +/// Trim anything from left of string +inline std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +/// Trim whitespace from right of string +inline std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim anything from right of string +inline std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast(wid)) << ""; + out << description; + } + out << "\n"; + return out; +} + +/// Verify the first character of an option +template bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; } + +/// Verify following characters of an option +template bool valid_later_char(T c) { + return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-'; +} + +/// Verify an option name +inline bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) + return false; + for(auto c : str.substr(1)) + if(!valid_later_char(c)) + return false; + return true; +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// Find and replace a substring with another substring +inline std::string find_and_replace(std::string str, std::string from, std::string to) { + + size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " +inline std::vector split_up(std::string str) { + + const std::string delims("\'\"`"); + auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; + trim(str); + + std::vector output; + bool embeddedQuote = false; + char keyChar = ' '; + while(!str.empty()) { + if(delims.find_first_of(str[0]) != std::string::npos) { + keyChar = str[0]; + auto end = str.find_first_of(keyChar, 1); + while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes + end = str.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + str = str.substr(end + 1); + } else { + output.push_back(str.substr(1)); + str = ""; + } + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it, str.end()); + } else { + output.push_back(str); + str = ""; + } + } + // transform any embedded quotes into the regular character + if(embeddedQuote) { + output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); + embeddedQuote = false; + } + trim(str); + } + return output; +} + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +inline std::string fix_newlines(std::string leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +inline size_t escape_detect(std::string &str, size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +/// Add quotes if the string contains spaces +inline std::string &add_quotes_if_needed(std::string &str) { + if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { + char quote = str.find('"') < str.find('\'') ? '\'' : '"'; + if(str.find(' ') != std::string::npos) { + str.insert(0, 1, quote); + str.append(1, quote); + } + } + return str; +} + +} // namespace detail +} // namespace CLI + +// From CLI/Error.hpp: + +namespace CLI { + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + int get_exit_code() const { return actual_exit_code; } + + std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually something like --help-all on command line +class CallForAllHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(size_t min_subcom) { + if(min_subcom == 1) + return RequiredError("A subcommand"); + else + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, size_t recieved) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(recieved)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(recieved)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + +} // namespace CLI + +// From CLI/TypeTools.hpp: + +namespace CLI { + +// Type tools + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. + +template using enable_if_t = typename std::enable_if::type; + +/// Check to see if something is a vector (fail check by default) +template struct is_vector { static const bool value = false; }; + +/// Check to see if something is a vector (true if actually a vector) +template struct is_vector> { static bool const value = true; }; + +/// Check to see if something is bool (fail check by default) +template struct is_bool { static const bool value = false; }; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool { static bool const value = true; }; + +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value && std::is_signed::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// This one should not be used, since vector types print the internal type +template ::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "VECTOR"; +} + +template ::value && !std::is_integral::value && !is_vector::value, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} + +// Lexical cast + +/// Signed integers / enums +template ::value && std::is_signed::value), detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + long long output_ll = std::stoll(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Unsigned integers +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + if(!input.empty() && input.front() == '-') + return false; // std::stoull happily converts negative values to junk without any errors. + + try { + size_t n = 0; + unsigned long long output_ll = std::stoull(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Floats +template ::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + output = static_cast(std::stold(input, &n)); + return n == input.size(); + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// String and similar +template ::value && !std::is_integral::value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + output = input; + return true; +} + +/// Non-string parsable +template ::value && !std::is_integral::value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + std::istringstream is; + + is.str(input); + is >> output; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +} // namespace detail +} // namespace CLI + +// From CLI/Split.hpp: + +namespace CLI { +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } else + return false; +} + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { + auto loc = current.find_first_of('='); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } else + return false; +} + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +inline bool split_windows(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } else + return false; +} + +// Splits a string into multiple long and short names +inline std::vector split_names(std::string current) { + std::vector output; + size_t val; + while((val = current.find(",")) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +/// Get a vector of short names, one of long names, and a single name +inline std::tuple, std::vector, std::string> +get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + + for(std::string name : input) { + if(name.length() == 0) + continue; + else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(pos_name.length() > 0) + throw BadNameString::MultiPositionalNames(name); + pos_name = name; + } + } + + return std::tuple, std::vector, std::string>( + short_names, long_names, pos_name); +} + +} // namespace detail +} // namespace CLI + +// From CLI/ConfigFwd.hpp: + +namespace CLI { + +class App; + +namespace detail { + +/// Comma separated join, adds quotes if needed +inline std::string ini_join(std::vector args) { + std::ostringstream s; + size_t start = 0; + for(const auto &arg : args) { + if(start++ > 0) + s << " "; + + auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace(ch, std::locale()); }); + if(it == arg.end()) + s << arg; + else if(arg.find(R"(")") == std::string::npos) + s << R"(")" << arg << R"(")"; + else + s << R"(')" << arg << R"(')"; + } + + return s.str(); +} + +} // namespace detail + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents; + + /// This is the name + std::string name; + + /// Listing of inputs + std::vector inputs; + + /// The list of parents and name joined by "." + std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Convert a flag to a bool + virtual std::vector to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + std::string val = item.inputs.at(0); + val = detail::to_lower(val); + + if(val == "true" || val == "on" || val == "yes") { + return std::vector(1); + } else if(val == "false" || val == "off" || val == "no") { + return std::vector(); + } else { + try { + size_t ui = std::stoul(val); + return std::vector(ui); + } catch(const std::invalid_argument &) { + throw ConversionError::TrueFalse(item.fullname()); + } + } + } else { + throw ConversionError::TooManyInputsFlag(item.fullname()); + } + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + std::vector from_file(const std::string &name) { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI files +class ConfigINI : public Config { + public: + std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; + + std::vector from_config(std::istream &input) const override { + std::string line; + std::string section = "default"; + + std::vector output; + + while(getline(input, line)) { + std::vector items_buffer; + + detail::trim(line); + size_t len = line.length(); + if(len > 1 && line[0] == '[' && line[len - 1] == ']') { + section = line.substr(1, len - 2); + } else if(len > 0 && line[0] != ';') { + output.emplace_back(); + ConfigItem &out = output.back(); + + // Find = in string, split and recombine + auto pos = line.find('='); + if(pos != std::string::npos) { + out.name = detail::trim_copy(line.substr(0, pos)); + std::string item = detail::trim_copy(line.substr(pos + 1)); + items_buffer = detail::split_up(item); + } else { + out.name = detail::trim_copy(line); + items_buffer = {"ON"}; + } + + if(detail::to_lower(section) != "default") { + out.parents = {section}; + } + + if(out.name.find('.') != std::string::npos) { + std::vector plist = detail::split(out.name, '.'); + out.name = plist.back(); + plist.pop_back(); + out.parents.insert(out.parents.end(), plist.begin(), plist.end()); + } + + out.inputs.insert(std::end(out.inputs), std::begin(items_buffer), std::end(items_buffer)); + } + } + return output; + } +}; + +} // namespace CLI + +// From CLI/Validators.hpp: + +namespace CLI { + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +struct Validator { + /// This is the type name, if empty the type name will not be changed + std::string tname; + + /// This it the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func; + + /// This is the required operator for a validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { return func(str); }; + + /// Combining validators is a new validator + Validator operator&(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func; + const std::function &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(!s1.empty() && !s2.empty()) + return s1 + " & " + s2; + else + return s1 + s2; + }; + return newval; + } + + /// Combining validators is a new validator + Validator operator|(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func; + const std::function &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(s1.empty() || s2.empty()) + return std::string(); + else + return s1 + " & " + s2; + }; + return newval; + } +}; + +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// Check for an existing file (returns error message if check fails) +struct ExistingFileValidator : public Validator { + ExistingFileValidator() { + tname = "FILE"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "File does not exist: " + filename; + } else if(is_dir) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing directory (returns error message if check fails) +struct ExistingDirectoryValidator : public Validator { + ExistingDirectoryValidator() { + tname = "DIR"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "Directory does not exist: " + filename; + } else if(!is_dir) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing path +struct ExistingPathValidator : public Validator { + ExistingPathValidator() { + tname = "PATH"; + func = [](const std::string &filename) { + struct stat buffer; + bool const exist = stat(filename.c_str(), &buffer) == 0; + if(!exist) { + return "Path does not exist: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an non-existing path +struct NonexistentPathValidator : public Validator { + NonexistentPathValidator() { + tname = "PATH"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + if(exist) { + return "Path already exists: " + filename; + } + return std::string(); + }; + } +}; +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Produce a range (factory). Min and max are inclusive. +struct Range : public Validator { + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Range(T min, T max) { + std::stringstream out; + out << detail::type_name() << " in [" << min << " - " << max << "]"; + + tname = out.str(); + func = [min, max](std::string input) { + T val; + detail::lexical_cast(input, val); + if(val < min || val > max) + return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); + + return std::string(); + }; + } + + /// Range of one value is 0 to value + template explicit Range(T max) : Range(static_cast(0), max) {} +}; + +namespace detail { +/// split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second everything +/// else +inline std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(!ExistingFile(commandline.substr(0, esp)).empty()) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + esp = commandline.find_first_of(' ', 1); + break; + } + } + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + // strip the program name + vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} +} // namespace detail +/// @} + +} // namespace CLI + +// From CLI/FormatterFwd.hpp: + +namespace CLI { + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, //< The normal, detailed help + All, //< A fully expanded help + Sub, //< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the first column + size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + virtual ~FormatterBase() = default; + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + else + return labels_.at(key); + } + + /// Get the current column width + size_t get_column_width() const { return column_width_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + virtual std::string make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App *, std::string, AppFormatMode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + +} // namespace CLI + +// From CLI/Option.hpp: + +namespace CLI { + +using results_t = std::vector; +using callback_t = std::function; + +class Option; +class App; + +using Option_p = std::unique_ptr