diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a3f49ed92..6b62d682a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,8 +1,5 @@ // For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.195.0/containers/cpp -// -// SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University -// SPDX-License-Identifier: Apache-2.0 { "name": "VILLASnode", "image": "registry.git.rwth-aachen.de/acs/public/villas/node/dev-vscode", diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 396de56d3..a67c7b2dc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -48,7 +48,7 @@ prepare:docker: TARGET: dev-vscode DOCKER_IMAGE_DEV: ${DOCKER_IMAGE}/dev-vscode tags: - - docker + - docker-image-builder # Stage: build @@ -92,10 +92,10 @@ test:python: stage: test script: - cd python - - /venv/bin/black --check . + - /venv/bin/pytest --verbose . + - /venv/bin/black --extend-exclude=".*(\\.pyi|_pb2.py)$" --check . + - /venv/bin/flake8 --extend-exclude="*.pyi,*_pb2.py" . - /venv/bin/mypy . - - /venv/bin/flake8 . - - /venv/bin/pytest -v . image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG} test:cppcheck: diff --git a/.reuse/dep5 b/.reuse/dep5 index 0852add48..70de7cb41 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -3,10 +3,6 @@ Upstream-Name: VILLASnode Upstream-Contact: Steffen Vogel Source: https://fein-aachen.org/en/projects/villas-node/ -Files: *.vi *.opal *.dft *.sib *.json *.ipynb doc/pictures/* clients/opal/models/send_receive/eonerc_logo.png doc/favicon.png -Copyright: 2018-2023, Institute for Automation of Complex Power Systems, RWTH Aachen University -License: Apache-2.0 - -Files: clients/rtds/**/*.txt clients/hypersim/*.ecf etc/labs/lab3.pcap packaging/live-iso/files/etc/* flake.lock tests/valgrind.supp packaging/archlinux/villas-node.install +Files: *.vi *.opal *.dft *.sib *.json *.ipynb *_pb2.py doc/pictures/* doc/favicon.png clients/opal/models/send_receive/eonerc_logo.png clients/rtds/**/*.txt clients/hypersim/*.ecf etc/labs/lab3.pcap packaging/live-iso/files/etc/* flake.lock tests/valgrind.supp packaging/archlinux/villas-node.install Copyright: 2018-2023, Institute for Automation of Complex Power Systems, RWTH Aachen University License: Apache-2.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d99a0a9b..c526b2bce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ find_package(RDMACM) find_package(Etherlab) find_package(Lua) find_package(LibDataChannel) +find_package(re) # Check for tools find_program(PASTE NAMES paste) @@ -117,13 +118,7 @@ pkg_check_modules(NANOMSG IMPORTED_TARGET nanomsg) if(NOT NANOMSG_FOUND) pkg_check_modules(NANOMSG IMPORTED_TARGET libnanomsg>=1.0.0) endif() -pkg_check_modules(RE IMPORTED_TARGET re>=2.9.0) -if(NOT RE_FOUND) - pkg_check_modules(RE IMPORTED_TARGET libre>=2.9.0) -endif() -if(NOT RE_FOUND) - pkg_check_modules(RE IMPORTED_TARGET re2) -endif() + if (REDISPP_FOUND) file(READ "${REDISPP_INCLUDEDIR}/sw/redis++/tls.h" CONTENTS) @@ -199,7 +194,7 @@ cmake_dependent_option(WITH_NODE_NANOMSG "Build with nanomsg node-type" cmake_dependent_option(WITH_NODE_NGSI "Build with ngsi node-type" "${WITH_DEFAULTS}" "" OFF) cmake_dependent_option(WITH_NODE_OPAL "Build with opal node-type" "${WITH_DEFAULTS}" "Opal_FOUND" OFF) cmake_dependent_option(WITH_NODE_REDIS "Build with redis node-type" "${WITH_DEFAULTS}" "HIREDIS_FOUND; REDISPP_FOUND" OFF) -cmake_dependent_option(WITH_NODE_RTP "Build with rtp node-type" "${WITH_DEFAULTS}" "RE_FOUND" OFF) +cmake_dependent_option(WITH_NODE_RTP "Build with rtp node-type" "${WITH_DEFAULTS}" "re_FOUND" OFF) cmake_dependent_option(WITH_NODE_SHMEM "Build with shmem node-type" "${WITH_DEFAULTS}" "HAS_SEMAPHORE; HAS_MMAN" OFF) cmake_dependent_option(WITH_NODE_SIGNAL "Build with signal node-type" "${WITH_DEFAULTS}" "" OFF) cmake_dependent_option(WITH_NODE_SMU "Build with smu node-type" "${WITH_DEFAULTS}" "" OFF) diff --git a/CODEOWNERS b/CODEOWNERS index 0d0cc9e4b..ef7dc9ef1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -13,20 +13,27 @@ # and not the global owner(s) will be requested for a review. # FPGA -fpga @n-eiling -/lib/nodes/fpga.cpp @n-eiling -/include/villas/nodes/fpga.hpp @n-eiling +fpga @n-eiling +/lib/nodes/fpga.cpp @n-eiling +/include/villas/nodes/fpga.hpp @n-eiling +/tools/hwdef-parse.py @n-eiling +/tools/fpga-* @n-eiling +/fpga @n-eiling +/etc/examples/nodes/fpga.conf @n-eiling +/etc/fpga @n-eiling +/doc/openapi/components/schemas/nodes/fpga.yaml @n-eiling + # PMU -/lib/nodes/libiec61850_goose.cpp @windrad6 -/include/villas/nodes/libiec61850_goose.hpp @windrad6 -/lib/nodes/uldaq.cpp @windrad6 -/include/villas/nodes/uldaq.hpp @windrad6 -/lib/dumper.cpp @windrad6 -/include/villas/dumper.hpp @windrad6 +/lib/nodes/libiec61850_goose.cpp @windrad6 +/include/villas/nodes/libiec61850_goose.hpp @windrad6 +/lib/nodes/uldaq.cpp @windrad6 +/include/villas/nodes/uldaq.hpp @windrad6 +/lib/dumper.cpp @windrad6 +/include/villas/dumper.hpp @windrad6 # VFIO related files -/common/lib/kernel/pci.cpp @n-eiling -/common/lib/kernel/vfi_*.cpp @n-eiling -/common/include/villas/kernel/pci.hpp @n-eiling -/common/include/villas/kernel/vfio_*.hpp @n-eiling +/common/lib/kernel/pci.cpp @n-eiling +/common/lib/kernel/vfi_*.cpp @n-eiling +/common/include/villas/kernel/pci.hpp @n-eiling +/common/include/villas/kernel/vfio_*.hpp @n-eiling diff --git a/common/include/villas/hist.hpp b/common/include/villas/hist.hpp index a2c5c0b94..3c7acb33e 100644 --- a/common/include/villas/hist.hpp +++ b/common/include/villas/hist.hpp @@ -34,7 +34,7 @@ public: // Count a value within its corresponding bucket. void put(double value); - // Calcluate the variance of all counted values. + // Calculate the variance of all counted values. double getVar() const; // Calculate the mean average of all counted values. @@ -43,8 +43,8 @@ public: // Calculate the standard derivation of all counted values. double getStddev() const; - // Print all statistical properties of distribution including a graphilcal plot of the histogram. - void print(Logger logger, bool details) const; + // Print all statistical properties of distribution including a graphical plot of the histogram. + void print(Logger logger, bool details, std::string prefix = "") const; // Print ASCII style plot of histogram. void plot(Logger logger) const; @@ -57,7 +57,7 @@ public: // Prints Matlab struct containing all infos to file. int dumpMatlab(FILE *f) const; - // Write the histogram in JSON format to fiel \p f. + // Write the histogram in JSON format to file \p f. int dumpJson(FILE *f) const; // Build a libjansson / JSON object of the histogram. diff --git a/common/include/villas/kernel/kernel.hpp b/common/include/villas/kernel/kernel.hpp index 30ad2800c..67db8e930 100644 --- a/common/include/villas/kernel/kernel.hpp +++ b/common/include/villas/kernel/kernel.hpp @@ -8,6 +8,7 @@ #pragma once #include +#include #include diff --git a/common/lib/hist.cpp b/common/lib/hist.cpp index 7ee048873..668973afa 100644 --- a/common/lib/hist.cpp +++ b/common/lib/hist.cpp @@ -97,28 +97,28 @@ double Hist::getVar() const { double Hist::getStddev() const { return sqrt(getVar()); } -void Hist::print(Logger logger, bool details) const { +void Hist::print(Logger logger, bool details, std::string prefix) const { if (total > 0) { Hist::cnt_t missed = total - higher - lower; - logger->info("Counted values: {} ({} between {} and {})", total, missed, + logger->info("{}Counted values: {} ({} between {} and {})", prefix, total, missed, low, high); - logger->info("Highest: {:g}", highest); - logger->info("Lowest: {:g}", lowest); - logger->info("Mu: {:g}", getMean()); - logger->info("1/Mu: {:g}", 1.0 / getMean()); - logger->info("Variance: {:g}", getVar()); - logger->info("Stddev: {:g}", getStddev()); + logger->info("{}Highest: {:g}", prefix, highest); + logger->info("{}Lowest: {:g}", prefix, lowest); + logger->info("{}Mu: {:g}", prefix, getMean()); + logger->info("{}1/Mu: {:g}", prefix, 1.0 / getMean()); + logger->info("{}Variance: {:g}", prefix, getVar()); + logger->info("{}Stddev: {:g}", prefix, getStddev()); if (details && total - higher - lower > 0) { char *buf = dump(); - logger->info("Matlab: {}", buf); + logger->info("{}Matlab: {}", prefix, buf); free(buf); plot(logger); } } else - logger->info("Counted values: {}", total); + logger->info("{}Counted values: {}", prefix, total); } void Hist::plot(Logger logger) const { @@ -128,7 +128,7 @@ void Hist::plot(Logger logger) const { std::vector cols = { {-9, TableColumn::Alignment::RIGHT, "Value", "%+9.3g"}, {-6, TableColumn::Alignment::RIGHT, "Count", "%6ju"}, - {0, TableColumn::Alignment::LEFT, "Plot", "%s", "occurences"}}; + {0, TableColumn::Alignment::LEFT, "Plot", "%s", "occurrences"}}; Table table = Table(logger, cols); diff --git a/etc/WSCC_9bus_MV.conf b/etc/WSCC_9bus_MV.conf index c666a6925..755d44462 100644 --- a/etc/WSCC_9bus_MV.conf +++ b/etc/WSCC_9bus_MV.conf @@ -53,9 +53,6 @@ paths = ( offset = 0, signals = ["f0", "f1", "f2", "f3"] } -# { -# type = "print" -# } ) } ) diff --git a/etc/eric-lab.conf b/etc/eric-lab.conf index 6c8c31a21..9cf9954ca 100644 --- a/etc/eric-lab.conf +++ b/etc/eric-lab.conf @@ -4,24 +4,21 @@ # Please note, that using all options at the same time does not really # makes sense. The purpose of this example is to serve as a reference. # -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files -# # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University # SPDX-License-Identifier: Apache-2.0 -name = "villas-acs" # The name of this VILLASnode. Might by used by node-types - # to identify themselves (default is the hostname). - +# The name of this VILLASnode. Might by used by node-types +# to identify themselves (default is the hostname) +name = "villas-acs" logging = { level = "debug" } http = { - port = 80 # Port for HTTP connections + # Port for HTTP connections + port = 80 } ## Dictionary of nodes diff --git a/etc/examples/example.conf b/etc/examples/example.conf index 945242b99..ca6075ee4 100644 --- a/etc/examples/example.conf +++ b/etc/examples/example.conf @@ -4,10 +4,6 @@ # Please note, that using all options at the same time does not really # makes sense. The purpose of this example is to serve as a reference. # -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files -# # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University # SPDX-License-Identifier: Apache-2.0 @@ -19,5 +15,5 @@ # An example node. Define multiple nodes in the "nodes" dictionary @include "nodes/socket.conf" -# A list of example paths. Define multiple paths by appending them to the "paths" list. +# A list of example paths. Define multiple paths by appending them to the "paths" list @include "paths.conf" diff --git a/etc/examples/global.conf b/etc/examples/global.conf index 0fbbb653f..6e3403d71 100644 --- a/etc/examples/global.conf +++ b/etc/examples/global.conf @@ -1,8 +1,4 @@ -# Global configuration file for VILLASnode. -# -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files +# Global configuration file for VILLASnode # # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University @@ -10,32 +6,39 @@ ## Global Options -affinity = 0x01 # Mask of cores the server should run on - # This also maps the NIC interrupts to those cores! +# Mask of cores the server should run on +# This also maps the NIC interrupts to those cores! +affinity = 0x01 -#priority = 50 # Priority for the server tasks. - # Usually the server is using a real-time FIFO - # scheduling algorithm +# Priority for the server tasks +# Usually the server is using a real-time FIFO +# scheduling algorithm - # See: https://github.com/docker/docker/issues/22380 - # on why we cant use real-time scheduling in Docker +# See: https://github.com/docker/docker/issues/22380 +# on why we cant use real-time scheduling in Docker +#priority = 50 -name = "villas-acs" # The name of this VILLASnode. Might by used by node-types - # to identify themselves (default is the hostname). +# The name of this VILLASnode. Might by used by node-types +# to identify themselves (default is the hostname) +name = "villas-acs" logging = { - level = "debug" # The level of verbosity for debug messages - # One of: "warn", "info", "error", "off", "info" + # The level of verbosity for debug messages + # One of: "warn", "info", "error", "off", "info" + level = "debug" + # File for logs + file = "/tmp/villas-node.log" - file = "/tmp/villas-node.log" # File for logs - - syslog = true # Log to syslogd + # Log to syslogd + syslog = true } http = { - enabled = true, # Do not listen on port if true + # Do not listen on port if true + enabled = true, - port = 80 # Port for HTTP connections + # Port for HTTP connections + port = 80 } diff --git a/etc/examples/hooks/digest.conf b/etc/examples/hooks/digest.conf index 75f7dcf5e..5789f25ca 100644 --- a/etc/examples/hooks/digest.conf +++ b/etc/examples/hooks/digest.conf @@ -9,7 +9,7 @@ paths = ( out = "file_node" hooks = ( - # Use a frame hook to generate NEW_FRAME annotations. + # Use a frame hook to generate NEW_FRAME annotations "frame", { type = "digest" diff --git a/etc/examples/hooks/dp.conf b/etc/examples/hooks/dp.conf index a59f54f8f..a9464c14e 100644 --- a/etc/examples/hooks/dp.conf +++ b/etc/examples/hooks/dp.conf @@ -13,8 +13,8 @@ paths = ( type = "dp" signal = "sine" - f0 = 50 # Hz - dt = 0.1 # seconds + f0 = 50 # In Hz + dt = 0.1 # In seconds # Alternative to dt # rate = 10 Hz diff --git a/etc/examples/hooks/gate.conf b/etc/examples/hooks/gate.conf index 36e9df7bb..911eb60ce 100644 --- a/etc/examples/hooks/gate.conf +++ b/etc/examples/hooks/gate.conf @@ -17,8 +17,8 @@ paths = ( threshold = 0.5 # Once triggered, keep active for: - duration = 5 # in seconds - samples = 100 # in number of samples + duration = 5 # In seconds + samples = 100 # In number of samples } ) } diff --git a/etc/examples/hooks/ip-dft-pmu.conf b/etc/examples/hooks/ip-dft-pmu.conf index 72dc0523a..de9a9c1a2 100644 --- a/etc/examples/hooks/ip-dft-pmu.conf +++ b/etc/examples/hooks/ip-dft-pmu.conf @@ -16,14 +16,23 @@ paths = ( "sine" ) - sample_rate = 1000, # sample rate of the input signal - dft_rate = 10, # number of phasors calculated per second + # Sample rate of the input signal + sample_rate = 1000, - estimation_range = 10, # the range around the nominal_freq in with the estimation is done - nominal_freq = 50, # the nominal grid frequnecy - number_plc = 10., # the number of power line cylces stored in the buffer + # Number of phasors calculated per second + dft_rate = 10, - angle_unit = "rad" # one of: rad, degree + # Yhe range around the nominal_freq in with the estimation is done + estimation_range = 10, + + # Yhe nominal grid frequnecy + nominal_freq = 50, + + # Yhe number of power line cylces stored in the buffer + number_plc = 10., + + # One of: rad, degree + angle_unit = "rad" } ) } diff --git a/etc/examples/hooks/lua.conf b/etc/examples/hooks/lua.conf index ca2adf33f..617192244 100644 --- a/etc/examples/hooks/lua.conf +++ b/etc/examples/hooks/lua.conf @@ -15,12 +15,12 @@ paths = ( type = "lua" # Enables or disables the use of signal names in the process() function - # of the Lua script. If disabled, numeric indices will be used. + # of the Lua script. If disabled, numeric indices will be used use_names = true # The Lua hook will pass the complete hook configuration to the prepare() # function. So you can add arbitrary settings here which are then - # consumed by the Lua script. + # consumed by the Lua script some_setting = "Hello World" this = { is = { @@ -39,14 +39,14 @@ paths = ( # # stop() Called when the node/path is stopped # - # restart() Called when the node/path is restarted. - # Falls back to stop() + start() if absent. + # restart() Called when the node/path is restarted + # Falls back to stop() + start() if absent # - # process(smp) Called for each sample which is being processed. + # process(smp) Called for each sample which is being processed # The sample is passed as a Lua table with the following # fields: - # - sequence The sequence number of the sample. - # - flags The flags field of the sample. + # - sequence The sequence number of the sample + # - flags The flags field of the sample # - ts_origin The origin timestamp as a Lua table containing # the following keys: # 0: seconds @@ -57,14 +57,14 @@ paths = ( # 1: nanoseconds # - data The sample data as a Lua table container either # numeric indices or the signal names depending - # on the 'use_names' option of the hook. + # on the 'use_names' option of the hook # - # periodic() Called periodically with the rate of the global 'stats' option. + # periodic() Called periodically with the rate of the global 'stats' option script = "../lua/hooks/test.lua" # Expression mode: We provide a mangled signal list including Lua expressions signals = ( - { name = "sum", type="float", unit = "V", expression = "smp.data.square * 10" }, + { name = "sum", type="float", unit = "V", expression = "smp.data.square * 10" }, # You can access any global variable set by the script { name = "sequence", type="float", unit = "V", expression = "global_var" }, @@ -76,7 +76,7 @@ paths = ( { name = "sum", type="float", unit = "V", expression = "math.sin(2 * math.pi * f * t)" }, { name = "random", expression = "smp.data.random" } - ) + ) }, { type = "print" diff --git a/etc/examples/hooks/pmu.conf b/etc/examples/hooks/pmu.conf index b915043ab..238bd002c 100644 --- a/etc/examples/hooks/pmu.conf +++ b/etc/examples/hooks/pmu.conf @@ -16,13 +16,20 @@ paths = ( "sine" ) - sample_rate = 1000, # sample rate of the input signal - dft_rate = 10, # number of phasors calculated per second + # Sample rate of the input signal + sample_rate = 1000, - nominal_freq = 50, # the nominal grid frequnecy - number_plc = 10., # the number of power line cylces stored in the buffer + # Number of phasors calculated per second + dft_rate = 10, - angle_unit = "rad" # one of: rad, degree + # The nominal grid frequnecy + nominal_freq = 50, + + # The number of power line cylces stored in the buffer + number_plc = 10., + + # One of: rad, degree + angle_unit = "rad" } ) } diff --git a/etc/examples/hooks/pmu_dft.conf b/etc/examples/hooks/pmu_dft.conf index 2b6f21b5d..0f1c669a5 100644 --- a/etc/examples/hooks/pmu_dft.conf +++ b/etc/examples/hooks/pmu_dft.conf @@ -16,21 +16,38 @@ paths = ( "sine" ) - sample_rate = 1000, # sample rate of the input signal - dft_rate = 10, # number of phasors calculated per second + # Sample rate of the input signal + sample_rate = 1000, - start_frequency = 49.7, # lowest frequency bin - end_frequency = 50.3, # highest frequency bin - frequency_resolution = 0.1, # frequency bin resolution + # Number of phasors calculated per second + dft_rate = 10, - window_size_factor = 1, # a factor with which the window will be increased - window_type = "hamming", # one of: flattop, hamming, hann - padding_type = "zero", # one of: signal_repeat, zero - frequency_estimate_type = "quadratic", # one of: quadratic + # Lowest frequency bin + start_frequency = 49.7, - pps_index = 0, # signal index of the PPS signal + # Highest frequency bin + end_frequency = 50.3, - angle_unit = "rad" # one of: rad, degree + # Frequency bin resolution + frequency_resolution = 0.1, + + # A factor with which the window will be increased + window_size_factor = 1, + + # One of: flattop, hamming, hann + window_type = "hamming", + + # One of: signal_repeat, zero + padding_type = "zero", + + # One of: quadratic + frequency_estimate_type = "quadratic", + + # Signal index of the PPS signal + pps_index = 0, + + # One of: rad, degree + angle_unit = "rad" } ) } diff --git a/etc/examples/hooks/print.conf b/etc/examples/hooks/print.conf index 9cb197349..466b8adc3 100644 --- a/etc/examples/hooks/print.conf +++ b/etc/examples/hooks/print.conf @@ -14,7 +14,7 @@ paths = ( output = "print_output_file.log" format = "villas.human" - # prefix = "[file_node] " # prefix and output are exclusive settings! + # prefix = "[file_node] " # Prefix and output are exclusive settings! } ) } diff --git a/etc/examples/nodes/api.conf b/etc/examples/nodes/api.conf index 35ed51237..7f4dccf99 100644 --- a/etc/examples/nodes/api.conf +++ b/etc/examples/nodes/api.conf @@ -8,16 +8,25 @@ nodes = { in = { signals = ( { - name = "" # Same as 'id' in uAPI context - description = "Volts on Bus A" # A human readable description of the channel - type = "float" # Same as 'datatype' in uAPI context + # Same as 'id' in uAPI context + name = "" + + # A human readable description of the channel + description = "Volts on Bus A" + + # Same as 'datatype' in uAPI context + type = "float" unit = "V" - payload = "events" # or 'samples' - rate = 100.0 # An expected refresh/sample rate of the signal + payload = "events" # Or 'samples' + + # An expected refresh/sample rate of the signal + rate = 100.0 + range = { min = 20.0 max = 100.0 } + readable = true writable = false } diff --git a/etc/examples/nodes/comedi.conf b/etc/examples/nodes/comedi.conf index d84679849..d77bb30e8 100644 --- a/etc/examples/nodes/comedi.conf +++ b/etc/examples/nodes/comedi.conf @@ -11,7 +11,7 @@ nodes = { rate = 1000, signals = ( - # note: order in this array defines order in villas sample + # Note: order in this array defines order in villas sample { channel = 0, range = 0, aref = 0, name = "temperature_int" }, { channel = 1, range = 0, aref = 0, name = "loopback_ao0" }, { channel = 2, range = 0, aref = 0, name = "loopback_ao1" }, @@ -23,11 +23,11 @@ nodes = { # Note: buffer size and rate shouldn't be changed at the moment # output sample rate rate = 40000, - # comedi write buffer in kilobytes + # Comedi write buffer in kilobytes bufsize = 24, signals = ( - # note: order in this array corresponds to order in villas sample + # Note: order in this array corresponds to order in villas sample { name = "ao0", channel = 0, range = 0, aref = 0 }, { name = "ao1", channel = 1, range = 0, aref = 0 }, { name = "ao2", channel = 2, range = 0, aref = 0 }, @@ -69,10 +69,10 @@ nodes = { paths = ( # 2-ch sine # { - # in = [ "sine1.data[0]", "sine2.data[0]" ] - # out = "pcie6259" - # rate = 10000 - # mask = () + # in = [ "sine1.data[0]", "sine2.data[0]" ] + # out = "pcie6259" + # rate = 10000 + # mask = () # } # Remote data via UDP diff --git a/etc/examples/nodes/ethercat.conf b/etc/examples/nodes/ethercat.conf index acca7e394..4081a5701 100644 --- a/etc/examples/nodes/ethercat.conf +++ b/etc/examples/nodes/ethercat.conf @@ -4,7 +4,7 @@ ethercat = { coupler = { position = 0 - vendor_id = 0x00000002 # Backhoff + vendor_id = 0x00000002 # Beckhoff product_code = 0x044c2c52 # EK1100 } diff --git a/etc/examples/nodes/file.conf b/etc/examples/nodes/file.conf index d64c6b880..609846ffd 100644 --- a/etc/examples/nodes/file.conf +++ b/etc/examples/nodes/file.conf @@ -3,31 +3,40 @@ nodes = { file_node = { - type = "file" + type = "file" - ### The following settings are specific to the file node-type!! ### - - uri = "logs/input.log", # These options specify the URI where the the files are stored - #uri = "logs/output_%F_%T.log" # The URI accepts all format tokens of (see strftime(3)) + # These options specify the URI where the the files are stored + # The URI accepts all format tokens of (see strftime(3)) + uri = "logs/input.log", + # uri = "logs/output_%F_%T.log" format = "csv" in = { - epoch_mode = "direct" # One of: direct (default), wait, relative, absolute - epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0). - # Consult the documentation of a full explanation + # One of: direct (default), wait, relative, absolute + epoch_mode = "direct" - rate = 2.0 # A constant rate at which the lines of the input files should be read - # A missing or zero value will use the timestamp in the first column - # of the file to determine the pause between consecutive lines. - eof = "rewind" # Rewind the file and start from the beginning. + # The interpretation of this value depends on epoch_mode (default is 0) + # Consult the documentation of a full explanation + epoch = 10 - buffer_size = 0 # Creates a stream buffer if value is positive + # A constant rate at which the lines of the input files should be read + # A missing or zero value will use the timestamp in the first column + # of the file to determine the pause between consecutive lines + rate = 2.0 + + # Rewind the file and start from the beginning + eof = "rewind" + + # Creates a stream buffer if value is positive + buffer_size = 0 }, out = { - flush = false # Flush or upload contents of the file every time new samples are sent. + # Flush or upload contents of the file every time new samples are sent + flush = false - buffer_size = 0 # Creates a stream buffer if value is positive + # Creates a stream buffer if value is positive + buffer_size = 0 } } } diff --git a/etc/examples/nodes/iec60870-5-104-sequence.conf b/etc/examples/nodes/iec60870-5-104-sequence.conf index b5e3ae12a..673c91640 100644 --- a/etc/examples/nodes/iec60870-5-104-sequence.conf +++ b/etc/examples/nodes/iec60870-5-104-sequence.conf @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 nodes = { - iec104_node_seq = { + iec104_node_seq = { type = "iec60870-5-104" address = "0.0.0.0" port = 2404 @@ -18,7 +18,7 @@ nodes = { # Interpret the duplicate IOAs as a sequence by incrementing the # IOA with each duplication. This only applies to two adjacent # signals with the same IOA. Specifying an IOA twice with other - # IOAs inbetween is an error. + # IOAs inbetween is an error duplicate_ioa_is_sequence = true } } diff --git a/etc/examples/nodes/iec60870-5-104.conf b/etc/examples/nodes/iec60870-5-104.conf index 66e4943c5..db36d1beb 100644 --- a/etc/examples/nodes/iec60870-5-104.conf +++ b/etc/examples/nodes/iec60870-5-104.conf @@ -3,7 +3,7 @@ nodes = { - iec104_node = { + iec104_node = { type = "iec60870-5-104" # Network address and port of the server @@ -22,14 +22,16 @@ nodes = { # Map signals to information object addresses and ASDU data types # one ASDU per specified asdu_type_id/asdu_type+with_timestamp is # send for each sample. Signals of the same type are collected - # in a single ASDU. + # in a single ASDU signals = ( { # The ASDU data type asdu_type = "normalized-float" - # add 56 bit unix timestamp to ASDU + + # Add 56 bit unix timestamp to ASDU with_timestamp = false - # the information object address of this signal + + # The information object address of this signal ioa = 4202832 }, { diff --git a/etc/examples/nodes/loopback.conf b/etc/examples/nodes/loopback.conf index 1c78af3fa..ab1ff9ebf 100644 --- a/etc/examples/nodes/loopback.conf +++ b/etc/examples/nodes/loopback.conf @@ -3,9 +3,14 @@ nodes = { loopback_node = { - type = "loopback", # A loopback node will receive exactly the same data which has been sent to it. - # The internal implementation is based on queue. - queuelen = 1024, # The queue length of the internal queue which buffers the samples. - mode = "polling" # Use busy polling for synchronization of the read and write side of the queue + # A loopback node will receive exactly the same data which has been sent to it + type = "loopback", + + # The internal implementation is based on queue + # The queue length of the internal queue which buffers the samples + queuelen = 1024, + + # Use busy polling for synchronization of the read and write side of the queue + mode = "polling" } } diff --git a/etc/examples/nodes/modbus.conf b/etc/examples/nodes/modbus.conf index 564b5fdb6..3c7fe9d59 100644 --- a/etc/examples/nodes/modbus.conf +++ b/etc/examples/nodes/modbus.conf @@ -8,20 +8,20 @@ nodes = { # Required transport type. Can be either "rtu" or "tcp" transport = "tcp" - # Optional timeout in seconds when waiting for responses from a modbus server. - # Default is 1.0. + # Optional timeout in seconds when waiting for responses from a modbus server + # Default is 1.0 response_timeout = 1.0 # - # Settings for transport = "tcp". + # Settings for transport = "tcp" # - # Required remote IP address. + # Required remote IP address remote = "127.0.0.1" - # Optional remote port. - # Default is 502. + # Optional remote port + # Default is 502 port = 502 @@ -29,10 +29,10 @@ nodes = { # Settings for transport = "rtu" # - # Required device file. + # Required device file device = "/dev/ttyS0" - # Required baudrate. + # Required baudrate baudrate = 9600 # Required parity. One of "none", "even" and "odd" @@ -44,102 +44,102 @@ nodes = { # Required stop bits. One of 1, 2 stop_bits = 1 - # The modbus unit ID. - # Required for transport = "rtu". - # Optional for transport = "tcp". + # The modbus unit ID + # Required for transport = "rtu" + # Optional for transport = "tcp" unit = 1 - # Optional polling rate for the modbus remote reads. - # Defaults to 10. + # Optional polling rate for the modbus remote reads + # Defaults to 10 rate = 10 in = { signals = ( - # A 32-bit IEEE 754 floating point value. - # This spans 2 registers. + # A 32-bit IEEE 754 floating point value + # This spans 2 registers { - # Required type = "float". + # Required type = "float" type = "float" - # Required address of the lowest register. + # Required address of the lowest register address = 0x50 - # Optional endianess for joining the 2 16-bit registers into a 32-bit value. - # Defaults to "big". + # Optional endianess for joining the 2 16-bit registers into a 32-bit value + # Defaults to "big" word_endianess = "big" - # Optional endianess for the 2 bytes within a register. - # Defaults to "big". + # Optional endianess for the 2 bytes within a register + # Defaults to "big" byte_endianess = "big" - # Optional scale that should be applied to the integer value. - # Defaults to 1. + # Optional scale that should be applied to the integer value + # Defaults to 1 scale = 10 - # Optional offset that should be applied to the integer value after scaling. - # Defaults to 0. + # Optional offset that should be applied to the integer value after scaling + # Defaults to 0 offset = 2 }, - # A single bit within a register as a boolean value. + # A single bit within a register as a boolean value { - # Required type = "boolean". + # Required type = "boolean" type = "boolean" - # Required address of the register. + # Required address of the register address = 0x54 - # Required bit within the register. - # Starting at 0. + # Required bit within the register + # Starting at 0 bit = 0 }, - # An integer value. - # This may span multiple registers. + # An integer value + # This may span multiple registers { - # Required type = "integer". + # Required type = "integer" type = "integer" - # Required address of the lowest register. + # Required address of the lowest register address = 0x52 - # Optional number of registers that should be joined to form the value. - # Defaults to 1. + # Optional number of registers that should be joined to form the value + # Defaults to 1 integer_registers = 1 - # Optional endianess for joining the 16-bit registers into a 32-bit value. - # Defaults to "big". + # Optional endianess for joining the 16-bit registers into a 32-bit value + # Defaults to "big" word_endianess = "big" - # Optional endianess for the 2 bytes within a register. - # Defaults to "big". + # Optional endianess for the 2 bytes within a register + # Defaults to "big" byte_endianess = "big" }, - # An float value created by reading an integer and applying an optional offset and scale. - # This may span multiple registers. + # An float value created by reading an integer and applying an optional offset and scale + # This may span multiple registers { - # Required type = "float". + # Required type = "float" type = "float" - # Required address of the lowest register. + # Required address of the lowest register address = 0x52 - # Required number of registers that should be joined to form the value. - # A "float" value without the "integer_registers" settings is considered an IEEE 754 float, spanning 2 registers. + # Required number of registers that should be joined to form the value + # A "float" value without the "integer_registers" settings is considered an IEEE 754 float, spanning 2 registers integer_registers = 1 - # Optional endianess for joining the 16-bit registers into a 32-bit value. - # Defaults to "big". + # Optional endianess for joining the 16-bit registers into a 32-bit value + # Defaults to "big" word_endianess = "big" - # Optional endianess for the 2 bytes within a register. - # Defaults to "big". + # Optional endianess for the 2 bytes within a register + # Defaults to "big" byte_endianess = "big" - # Optional scale that should be applied to the integer value. - # Defaults to 1. + # Optional scale that should be applied to the integer value + # Defaults to 1 scale = 10 - # Optional offset that should be applied to the integer value after scaling. - # Defaults to 0. + # Optional offset that should be applied to the integer value after scaling + # Defaults to 0 offset = 2 } ) @@ -147,18 +147,18 @@ nodes = { out = { signals = ( - # All register mappings described for "in" except for "boolean" are supported in the "out" signals. + # All register mappings described for "in" except for "boolean" are supported in the "out" signals { type = "float" address = 0x50 - # Scale and offset a applied as attributes of the register that is written to. + # Scale and offset a applied as attributes of the register that is written to # This means the value written to "register" for a "signal" with "offset" and "scale" will be: # # register = (signal - offset) / scale # - # It is fairly common to specify a scale and offset for modbus registers in a device manual. - # You should be able to plug those values into this configuration without conversion. + # It is fairly common to specify a scale and offset for modbus registers in a device manual + # You should be able to plug those values into this configuration without conversion scale = 10 offset = 2 }, diff --git a/etc/examples/nodes/mqtt.conf b/etc/examples/nodes/mqtt.conf index 53728fcb1..9fa35b20b 100644 --- a/etc/examples/nodes/mqtt.conf +++ b/etc/examples/nodes/mqtt.conf @@ -12,7 +12,8 @@ nodes = { host = "localhost", port = 1883, - keepalive = 5, # Send ping every 5 seconds to keep connection alive + # Send ping every 5 seconds to keep connection alive + keepalive = 5, retain = false, qos = 0, diff --git a/etc/examples/nodes/nanomsg.conf b/etc/examples/nodes/nanomsg.conf index 786ec7c73..40e751d6e 100644 --- a/etc/examples/nodes/nanomsg.conf +++ b/etc/examples/nodes/nanomsg.conf @@ -7,9 +7,14 @@ nodes = { out = { endpoints = [ - "tcp://*:12000", # TCP socket - "ipc:///tmp/test.ipc", # Interprocess communication - "inproc://test" # Inprocess communication + # TCP socket + "tcp://*:12000", + + # Interprocess communication + "ipc:///tmp/test.ipc", + + # Inprocess communication + "inproc://test" ], } in = { diff --git a/etc/examples/nodes/netem.conf b/etc/examples/nodes/netem.conf index b02a7849c..41c4b09bf 100644 --- a/etc/examples/nodes/netem.conf +++ b/etc/examples/nodes/netem.conf @@ -2,28 +2,39 @@ # SPDX-License-Identifier: Apache-2.0 nodes = { - udp_node = { # The dictionary is indexed by the name of the node. - type = "socket", # For a list of available node-types run: 'villas-node -h' + udp_node = { + type = "socket", - ### The following settings are specific to the socket node-type!! ### - - format = "gtnet", # For a list of available node-types run: 'villas-node -h' + format = "gtnet", in = { - address = "127.0.0.1:12001" # This node only received messages on this IP:Port pair + address = "127.0.0.1:12001" }, out = { - address = "127.0.0.1:12000", # This node sends outgoing messages to this IP:Port pair + address = "127.0.0.1:12000", - netem = { # Network emulation settings + # Network emulation settings + # Those settings can be specified for each node individually! + netem = { enabled = true, - # Those settings can be specified for each node individually! - delay = 100000, # Additional latency in microseconds - jitter = 30000, # Jitter in uS - distribution = "normal", # Distribution of delay: uniform, normal, pareto, paretonormal - loss = 10 # Packet loss in percent - duplicate = 10, # Duplication in percent - corrupt = 10 # Corruption in percent + + # Additional latency in microseconds + delay = 100000, + + # Jitter in uS + jitter = 30000, + + # Distribution of delay: uniform, normal, pareto, paretonormal + distribution = "normal", + + # Packet loss in percent + loss = 10 + + # Duplication in percent + duplicate = 10, + + # Corruption in percent + corrupt = 10 } } } diff --git a/etc/examples/nodes/ngsi.conf b/etc/examples/nodes/ngsi.conf index 46b9cf512..8c468e36f 100644 --- a/etc/examples/nodes/ngsi.conf +++ b/etc/examples/nodes/ngsi.conf @@ -5,28 +5,33 @@ nodes = { ngsi_node = { type = "ngsi", - ### The following settings are specific to the ngsi node-type!! ### - # The HTTP REST API endpoint of the FIRWARE context broker endpoint = "http://46.101.131.212:1026", - access_token: "aig1aaQuohsh5pee9uiC2Bae3loSh9wu" # Add an 'Auth-Token' token header to each request + # Add an 'Auth-Token' token header to each request + access_token = "aig1aaQuohsh5pee9uiC2Bae3loSh9wu" entity_id = "S3_ElectricalGrid", entity_type = "ElectricalGridMonitoring", - create = true # Create the NGSI entities during startup + # Create the NGSI entities during startup + create = true - rate = 0.1 # Rate at which we poll the broker for updates - timeout = 1, # Timeout of HTTP request in seconds (default is 1, must be smaller than 1 / rate) - verify_ssl = false, # Verification of SSL server certificates (default is true) + # Rate at which we poll the broker for updates + rate = 0.1 + + # Timeout of HTTP request in seconds (default is 1, must be smaller than 1 / rate) + timeout = 1, + + # Verification of SSL server certificates (default is true) + verify_ssl = false, in = { signals = ( { name = "attr1", - ngsi_attribute_name = "attr1", # defaults to signal 'name' - ngsi_attribute_type = "Volts", # default to signal 'unit' + ngsi_attribute_name = "attr1", # Defaults to signal 'name' + ngsi_attribute_type = "Volts", # Default to signal 'unit' ngsi_attribute_metadatas = ( { name="accuracy", type="percent", value="5" } ) diff --git a/etc/examples/nodes/opal.conf b/etc/examples/nodes/opal.conf index 68eb78604..40ace6a7e 100644 --- a/etc/examples/nodes/opal.conf +++ b/etc/examples/nodes/opal.conf @@ -2,38 +2,17 @@ # SPDX-License-Identifier: Apache-2.0 nodes = { - opal_node = { # The server can be started as an Asynchronous process - type = "opal", # from within an OPAL-RT model. + # The server can be started as an Asynchronous process + # from within an OPAL-RT model + opal_node = { + type = "opal", - ### The following settings are specific to the opal node-type!! ### + # It's possible to have multiple send / recv Icons per model + send_id = 1, + + # Specify the ID here + recv_id = 1, - send_id = 1, # It's possible to have multiple send / recv Icons per model - recv_id = 1, # Specify the ID here. reply = true - }, - file_node = { - type = "file", - - ### The following settings are specific to the file node-type!! ### - - uri = "logs/input.log", # These options specify the path prefix where the the files are stored - - in = { - epoch_mode = "direct" # One of: direct (default), wait, relative, absolute - epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0). - # Consult the documentation of a full explanation - - rate = 2.0 # A constant rate at which the lines of the input files should be read - # A missing or zero value will use the timestamp in the first column - # of the file to determine the pause between consecutive lines. - - buffer_size = 1000000 - - eof = "rewind" # One of: rewind, exit (default) or wait - }, - out = { - flush = true - buffer_size = 1000000 - } } } diff --git a/etc/examples/nodes/redis.conf b/etc/examples/nodes/redis.conf index c7457c63c..d17862d57 100644 --- a/etc/examples/nodes/redis.conf +++ b/etc/examples/nodes/redis.conf @@ -5,25 +5,36 @@ nodes = { redis_node = { type = "redis", - format = "json", # only valid for mode = 'channel' and 'key' - # With mode = 'hash' we will use a simple human readable format + # Only valid for mode = 'channel' and 'key' + # With mode = 'hash' we will use a simple human readable format + format = "json", - key = "my_key" # The Redis key to be used for mode = 'key' or 'hash' (default is the node name) - channel = "my_channel" # the Redis channel tp be used for mode = 'channel' (default is the node name) + # The Redis key to be used for mode = 'key' or 'hash' (default is the node name) + key = "my_key" - mode = "key", # one of: - # - 'channel' (publish/subscribe) - # - 'key' (set/get) - # - 'hash' (hmset/hgetall) + # The Redis channel tp be used for mode = 'channel' (default is the node name) + channel = "my_channel" - notify = false # Whether or not to use Redis keyspace event notifications to get notified about updates + # One of: + # - 'channel' (publish/subscribe) + # - 'key' (set/get) + # - 'hash' (hmset/hgetall) + mode = "key", - rate = 1.0 # The polling rate when notify = false + # Whether or not to use Redis keyspace event notifications to get notified about updates + notify = false - uri = "tcp://localhost:6379/0", # The Redis connection URI + # The polling rate when notify = false + rate = 1.0 - # host = "localhost" # Alternatively the connection options can be specified independently - # port = 6379 # Note: options here will overwrite the respective part of the URI if both are given. + # The Redis connection URI + uri = "tcp://localhost:6379/0", + + # Alternatively the connection options can be specified independently + # host = "localhost" + + # Note: options here will overwrite the respective part of the URI if both are given + # port = 6379 # db = 0 # path = "/var/run/redis.sock" @@ -32,11 +43,11 @@ nodes = { # password = "guest" # ssl = { - # enabled: true - # cacert: "/etc/ssl/certs/ca-certificates.crt", - # cacertdir: "/etc/ssl/certs" - # cert: "./my_cert.crt", - # key, "./my_key.key" + # enabled = true + # cacert = "/etc/ssl/certs/ca-certificates.crt", + # cacertdir = "/etc/ssl/certs" + # cert = "./my_cert.crt", + # key = "./my_key.key" # } } } diff --git a/etc/examples/nodes/rtp.conf b/etc/examples/nodes/rtp.conf index 8c1fd626c..b7120cbf4 100644 --- a/etc/examples/nodes/rtp.conf +++ b/etc/examples/nodes/rtp.conf @@ -41,11 +41,15 @@ nodes = { out = { address = "127.0.0.1:12000" - netem = { # Network emulation settings + # Network emulation settings + netem = { enabled = false, - delay = 100000, # Additional latency in microseconds - loss = 10 # Packet loss in percent + # Additional latency in microseconds + delay = 100000, + + # Packet loss in percent + loss = 10 } } } diff --git a/etc/examples/nodes/shmem.conf b/etc/examples/nodes/shmem.conf index 773d0a42a..487a5f1af 100644 --- a/etc/examples/nodes/shmem.conf +++ b/etc/examples/nodes/shmem.conf @@ -6,18 +6,23 @@ nodes = { type = "shmem", in = { + # Name of shared memory segment for receiving side name = "sn1_in" - }, # Name of shared memory segment for receiving side + }, out = { - name = "sn1_in" # Name of shared memory segment for sending side + # Name of shared memory segment for sending side + name = "sn1_in" }, - queuelen = 1024, # Length of the queues - mode = "pthread", # We can busy-wait or use pthread condition variables for synchronizations + # Length of the queues + queuelen = 1024, + + # We can busy-wait or use pthread condition variables for synchronizations + mode = "pthread", # Execute an external process when starting the node which # then starts the other side of this shared memory channel - # Usually we also pass the shmem names as parameters. + # Usually we also pass the shmem names as parameters exec = [ "villas-shmem", "sn1_in", "sn1_out" ] } } diff --git a/etc/examples/nodes/signal-v2.conf b/etc/examples/nodes/signal-v2.conf index 00f0c97a7..a80b9fedb 100644 --- a/etc/examples/nodes/signal-v2.conf +++ b/etc/examples/nodes/signal-v2.conf @@ -6,9 +6,15 @@ nodes = { type = "signal.v2", rate = 10.0 - realtime = true, # Wait between emitting each sample - limit = 1000, # Only emit 1000 samples, then stop - monitor_missed = true # Count and warn about missed steps + + # Wait between emitting each sample + realtime = true, + + # Only emit 1000 samples, then stop + limit = 1000, + + # Count and warn about missed steps + monitor_missed = true in = { signals = ( diff --git a/etc/examples/nodes/signal.conf b/etc/examples/nodes/signal.conf index a2a1a477f..8501f7bd8 100644 --- a/etc/examples/nodes/signal.conf +++ b/etc/examples/nodes/signal.conf @@ -8,14 +8,31 @@ nodes = { # One of "sine", "square", "ramp", "triangle", "random", "mixed", "counter" signal = [ "sine", "pulse", "square" ], - values = 3, # Number of values per sample - amplitude = [ 1.2, 0.0, 4.0 ], # Amplitude of generated signals - frequency = 10, # Frequency of generated signals - stddev = 2, # Standard deviation of random signals (normal distributed) - rate = 10.0, # Sample rate - offset = 1.0, # Constant offset - realtime = true, # Wait between emitting each sample - limit = 1000, # Only emit 1000 samples, then stop - monitor_missed = true # Count and warn about missed steps + # Number of values per sample + values = 3, + + # Amplitude of generated signals + amplitude = [ 1.2, 0.0, 4.0 ], + + # Frequency of generated signals in Hz + frequency = 10, + + # Standard deviation of random signals (normal distributed) + stddev = 2, + + # Sample rate in Hz + rate = 10.0, + + # Constant offset + offset = 1.0, + + # Wait between emitting each sample + realtime = true, + + # Only emit 1000 samples, then stop + limit = 1000, + + # Count and warn about missed steps + monitor_missed = true } } diff --git a/etc/examples/nodes/socket.conf b/etc/examples/nodes/socket.conf index cc613b1a5..b83d5e911 100644 --- a/etc/examples/nodes/socket.conf +++ b/etc/examples/nodes/socket.conf @@ -2,78 +2,94 @@ # SPDX-License-Identifier: Apache-2.0 nodes = { - udp_node = { # The dictionary is indexed by the name of the node. - type = "socket", # For a list of available node-types run: 'villas-node -h' - vectorize = 30, # Receive and sent 30 samples per message (combining). - samplelen = 10 # The maximum number of samples this node can receive + udp_node = { + type = "socket", - builtin = false, # By default, all nodes will have a few builtin hooks attached to them. - # When collecting statistics or measurements these are undesired. + # Receive and sent 30 samples per message (combining) + vectorize = 30, - ### The following settings are specific to the socket node-type!! ### + # The maximum number of samples this node can receive + samplelen = 10 - layer = "udp", # Layer can be one of: - # - udp Send / receive L4 UDP packets - # - ip Send / receive L3 IP packets - # - eth Send / receive L2 Ethernet frames (IEEE802.3) + # By default, all nodes will have a few builtin hooks attached to them + # When collecting statistics or measurements these are undesired + builtin = false, - format = "gtnet", # For a list of available node-types run: 'villas-node -h' + # Layer can be one of: + # - udp Send / receive L4 UDP packets + # - ip Send / receive L3 IP packets + # - eth Send / receive L2 Ethernet frames (IEEE802.3) + layer = "udp", + + + format = "gtnet", in = { - address = "127.0.0.1:12001" # This node only received messages on this IP:Port pair + # This node only received messages on this IP:Port pair + address = "127.0.0.1:12001" - verify_source = true # Check if source address of incoming packets matches the remote address. + # Check if source address of incoming packets matches the remote address + verify_source = true }, out = { - address = "127.0.0.1:12000", # This node sends outgoing messages to this IP:Port pair + # This node sends outgoing messages to this IP:Port pair + address = "127.0.0.1:12000", } } + # Raw Ethernet frames ethernet_node = { - type = "socket", # See above. + type = "socket", - ### The following settings are specific to the socket node-type!! ### - - layer = "eth", + layer = "eth", in = { - address = "12:34:56:78:90:AB%lo:12002" + address = "12:34:56:78:90:AB%lo:12002" }, out = { address = "12:34:56:78:90:AB%lo:12002" } }, + # Datagram UNIX domain sockets require two endpoints unix_domain_node = { - type = "socket", - layer = "unix", # Datagram UNIX domain sockets require two endpoints + type = "socket", + layer = "unix", in = { address = "/var/run/villas-node/node.sock" }, out = { - address = "/var/run/villas-node/client.sock" + address = "/var/run/villas-node/client.sock" } } - udp_multicast_node = { # The dictionary is indexed by the name of the node. - type = "socket", # For a list of available node-types run: 'villas-node -h' - - ### The following settings are specific to the socket node-type!! ### + udp_multicast_node = { + type = "socket", in = { - address = "127.0.0.1:12001" # This node only received messages on this IP:Port pair + # This node only received messages on this IP:Port pair + address = "127.0.0.1:12001" - multicast = { # IGMP multicast is only support for layer = (ip|udp) - enabled = true, + # IGMP multicast is only support for layer = (ip|udp) + multicast = { + enabled = true, - group = "224.1.2.3", # The multicast group. Must be within 224.0.0.0/4 - interface = "1.2.3.4", # The IP address of the interface which should receive multicast packets. - ttl = 128, # The time to live for outgoing multicast packets. - loop = false, # Whether or not to loopback outgoing multicast packets to the local host. + # The multicast group. Must be within 224.0.0.0/4 + group = "224.1.2.3", + + # The IP address of the interface which should receive multicast packets + interface = "1.2.3.4", + + # The time to live for outgoing multicast packets + ttl = 128, + + # Whether or not to loopback outgoing multicast packets to the local host + loop = false, } }, out = { - address = "127.0.0.1:12000", # This node sends outgoing messages to this IP:Port pair + # This node sends outgoing messages to this IP:Port pair + address = "127.0.0.1:12000", } } } diff --git a/etc/examples/nodes/test_rtt.conf b/etc/examples/nodes/test_rtt.conf index a9c2aaed0..60c7ce910 100644 --- a/etc/examples/nodes/test_rtt.conf +++ b/etc/examples/nodes/test_rtt.conf @@ -2,44 +2,50 @@ # SPDX-License-Identifier: Apache-2.0 nodes = { - rtt_node = { # The "test_rtt" node-type runs a set of test cases for varying - type = "test_rtt", # sending rates, number of values and generates statistics. - cooldown = 2, # The cooldown time between each test case in seconds - prefix = "test_rtt_%y-%m-%d_%H-%M-%S", # An optional prefix in the filename - output = "./results", # The output directory for all results - # The results of each test case will be written to a separate file. - format = "villas.human", # The output format of the result files. + # The "test_rtt" node-type runs a set of test cases for varying + # sending rates, number of values and generates statistics + # The cooldown time between each test case in seconds + rtt_node = { + type = "test_rtt", + cooldown = 2, - cases = ( # The list of test cases - # Each test case can specify a single or an array of rates and values - # If arrays are used, we will generate multiple test cases with all - # possible combinations + # An optional prefix in the filename + prefix = "test_rtt_%y-%m-%d_%H-%M-%S", + + # The output directory for all results + # The results of each test case will be written to a separate file + output = "./results", + + # The output format of the result files + format = "villas.human", + + # The list of test cases + # Each test case can specify a single or an array of rates and values + # If arrays are used, we will generate multiple test cases with all + # possible combinations + cases = ( { - rates = 55.0, # The sending rate in Hz - values = [ 5, 10, 20], # The number of values which should be send in each sample - limit = 100 # The number of samples which should be send during this test case + # The sending rate in Hz + rates = 55.0, + + # The number of values which should be send in each sample + values = [ 5, 10, 20], + + # The number of samples which should be send during this test case + limit = 100 }, { - rates = [ 5, 10, 30 ], # An array of rates in Hz - values = [ 2, 10, 20 ],# An array of number of values - duration = 5 # The duration of the test case in seconds (depending on the sending rate) + # An array of sending rates in Hz + rates = [ 5, 10, 30 ], + + # An array of number of values + values = [ 2, 10, 20 ], + + # The duration of the test case in seconds (depending on the sending rate) + duration = 5 } ) } } - -paths = ( - { - # Simple loopback path to test the node - in = "rtt_node" - out = "rtt_node" - - # hooks = ( - # { - # type = "print" - # } - # ) - } -) diff --git a/etc/examples/nodes/unix_domain.conf b/etc/examples/nodes/unix_domain.conf index 2ebd1a585..785262f8e 100644 --- a/etc/examples/nodes/unix_domain.conf +++ b/etc/examples/nodes/unix_domain.conf @@ -3,9 +3,9 @@ nodes = { unix_domain_node = { - type = "socket", - layer = "unix", - format = "protobuf", + type = "socket", + layer = "unix", + format = "protobuf", in = { address = "/var/run/villas-node.server.sock" diff --git a/etc/examples/nodes/webrtc.conf b/etc/examples/nodes/webrtc.conf index 64928e058..c3cec5a94 100644 --- a/etc/examples/nodes/webrtc.conf +++ b/etc/examples/nodes/webrtc.conf @@ -13,16 +13,16 @@ nodes = { # Address to the websocket signaling server server = "https://villas.k8s.eonerc.rwth-aachen.de/ws/signaling" - # Limit the number of times a channel will retransmit data if not successfully delivered. - # This value may be clamped if it exceeds the maximum value supported. + # Limit the number of times a channel will retransmit data if not successfully delivered + # This value may be clamped if it exceeds the maximum value supported max_retransmits = 0 # Number of seconds to wait for a WebRTC connection before proceeding the start # of VILLASnode. Mainly used for testing - wait_seconds = 10 # in seconds + wait_seconds = 10 # In seconds - # Indicates if data is allowed to be delivered out of order. - # The default value of false, does not make guarantees that data will be delivered in order. + # Indicates if data is allowed to be delivered out of order + # The default value of false, does not make guarantees that data will be delivered in order ordered = false # Setting for Interactive Connectivity Establishment diff --git a/etc/examples/nodes/zeromq.conf b/etc/examples/nodes/zeromq.conf index 30102e797..84aa6b527 100644 --- a/etc/examples/nodes/zeromq.conf +++ b/etc/examples/nodes/zeromq.conf @@ -5,27 +5,38 @@ nodes = { zeromq_node = { type = "zeromq" - pattern = "pubsub" # The ZeroMQ pattern. One of pubsub, radiodish - ipv6 = false # Enable IPv6 support + # The ZeroMQ pattern. One of pubsub, radiodish + pattern = "pubsub" - curve = { # Z85 encoded Curve25519 keys + # Enable IPv6 support + ipv6 = false + + # Z85 encoded Curve25519 keys + curve = { enabled = false, public_key = "Veg+Q.V-c&1k>yVh663gQ^7fL($y47gybE-nZP1L" secret_key = "HPY.+mFuB[jGs@(zZr6$IZ1H1dZ7Ji*j>oi@O?Pc" } in = { - subscribe = "tcp://*:1234" # The subscribe endpoint. - # See http://api.zeromq.org/2-1:zmq-bind for details. - filter = "ab184" # A filter which is prefix matched for each received msg + # The subscribe endpoint + # See http://api.zeromq.org/2-1:zmq-bind for details + subscribe = "tcp://*:1234" + + # A filter which is prefix matched for each received msg + filter = "ab184" } out = { - publish = [ # The publish endpoints. - "tcp://localhost:1235", # See http://api.zeromq.org/2-1:zmq-connect for details. + # The publish endpoints + # See http://api.zeromq.org/2-1:zmq-connect for details + publish = [ + + "tcp://localhost:1235", "tcp://localhost:12444" ] - filter = "ab184" # A prefix which is pre-pended to each message. + # A prefix which is pre-pended to each message + filter = "ab184" } } } diff --git a/etc/examples/paths.conf b/etc/examples/paths.conf index 94933dd07..b704c6c88 100644 --- a/etc/examples/paths.conf +++ b/etc/examples/paths.conf @@ -3,19 +3,29 @@ paths = ( { - enabled = true, # Enable this path (default: true) - reverse = true, # Setup a path in the reverse direction as well (default: false) + # Enable this path (default: true) + enabled = true, - in = "udp_node", # Name of the node we receive messages from (see node dictionary) - out = "ethernet_node", # Name of the node we send messages to. + # Setup a path in the reverse direction as well (default: false) + reverse = true, - rate = 10.0 # A rate at which this path will be triggered if no input node receives new data + # Name of the node we receive messages from (see node dictionary) + in = "udp_node", + + # Name of the node we send messages to + out = "ethernet_node", + + # A rate at which this path will be triggered if no input node receives new data + rate = 10.0 queuelen = 128, - mode = "all", # When this path should be triggered - # - "all": After all masked input nodes received new data - # - "any": After any of the masked input nodes received new data - mask = [ "udp_node" ], # A list of input nodes which will trigger the path + # When this path should be triggered + # - "all": After all masked input nodes received new data + # - "any": After any of the masked input nodes received new data + mode = "all", + + # A list of input nodes which will trigger the path + mask = [ "udp_node" ], } ) diff --git a/etc/gtnet-skt/emulate_gtnet.conf b/etc/gtnet-skt/emulate_gtnet.conf index 4e15b6eab..131534d7a 100644 --- a/etc/gtnet-skt/emulate_gtnet.conf +++ b/etc/gtnet-skt/emulate_gtnet.conf @@ -1,8 +1,4 @@ -#* GTNET-SKT test configuration. -# -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files +#* GTNET-SKT test configuration # # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University @@ -18,7 +14,7 @@ nodes = { format = "gtnet" in = { - address = "*:12000" # Local ip:port, use '*' for random port + address = "*:12000" } out = { address = "134.130.169.80:12001" diff --git a/etc/gtnet-skt/test1.conf b/etc/gtnet-skt/test1.conf index e416d4410..295f1b612 100644 --- a/etc/gtnet-skt/test1.conf +++ b/etc/gtnet-skt/test1.conf @@ -1,8 +1,4 @@ -# GTNET-SKT test configuration. -# -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files +# GTNET-SKT test configuration # # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University @@ -18,7 +14,7 @@ nodes = { format = "villas.binary" in = { - address = "192.168.88.128:12002" # Local ip:port, use '*' for random port + address = "192.168.88.128:12002" } out = { address = "192.168.88.129:12001" @@ -26,7 +22,7 @@ nodes = { netem = { enabled = false - delay = 1000000 # In micro seconds! + delay = 1000000 jitter = 300000 distribution = "normal" } @@ -37,7 +33,7 @@ nodes = { format = "villas.binary" in = { - address = "*:12004" # Local ip:port, use '*' for random port + address = "*:12004" } out = { address = "192.168.88.129:12005" @@ -47,8 +43,8 @@ nodes = { paths = ( { - in = "node1" # Name of the node we listen to (see above) - out = "node1" # And we loop back to the origin + in = "node1" + out = "node1" hooks = ( { diff --git a/etc/gtnet-skt/test2.conf b/etc/gtnet-skt/test2.conf index 0e16b1820..d1e2456bf 100644 --- a/etc/gtnet-skt/test2.conf +++ b/etc/gtnet-skt/test2.conf @@ -1,8 +1,4 @@ -# GTNET-SKT test configuration. -# -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files +# GTNET-SKT test configuration # # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University @@ -18,7 +14,7 @@ nodes = { format = "villas.binary" in = { - address = "192.168.88.128:12002" # Local ip:port, use '*' for random port + address = "192.168.88.128:12002" } out = { address = "192.168.88.129:12001" @@ -26,7 +22,7 @@ nodes = { netem = { enabled = false - delay = 1000000 # In micro seconds! + delay = 1000000 jitter = 300000 distribution = "normal" } @@ -37,7 +33,7 @@ nodes = { format = "villas.binary" in = { - address = "192.168.88.128:12004" # Local ip:port, use '*' for random port + address = "192.168.88.128:12004" } out = { address = "192.168.88.129:12001" @@ -47,8 +43,8 @@ nodes = { paths = ( { - in = "node1" # Name of the node we listen to (see above) - out = "node2" # And we loop back to the origin + in = "node1" + out = "node2" hooks = ( { diff --git a/etc/gtnet-skt/test3.conf b/etc/gtnet-skt/test3.conf index 4bc34f745..f233f9296 100644 --- a/etc/gtnet-skt/test3.conf +++ b/etc/gtnet-skt/test3.conf @@ -1,8 +1,4 @@ -# GTNET-SKT test configuration. -# -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files +# GTNET-SKT test configuration # # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University @@ -19,7 +15,7 @@ nodes = { format = "gtnet" in = { - address = "192.168.88.128:12002" # Local ip:port, use '*' for random port + address = "192.168.88.128:12002" } out = { address = "192.168.88.129:12001" @@ -27,7 +23,7 @@ nodes = { netem = { enabled = false - delay = 1000000 # In micro seconds! + delay = 1000000 jitter = 300000 distribution = "normal" } @@ -38,7 +34,7 @@ nodes = { format = "gtnet" in = { - address = "192.168.88.128:12004" # Local ip:port, use '*' for random port + address = "192.168.88.128:12004" } out = { address = "192.168.88.129:12001" @@ -48,8 +44,8 @@ nodes = { paths = ( { - in = "node1" # Name of the node we listen to (see above) - out = "node2" # And we loop back to the origin + in = "node1" + out = "node2" hooks = ( { diff --git a/etc/gtnet-skt/test4.conf b/etc/gtnet-skt/test4.conf index 11e1cd1af..27a225f51 100644 --- a/etc/gtnet-skt/test4.conf +++ b/etc/gtnet-skt/test4.conf @@ -1,8 +1,4 @@ -# GTNET-SKT test configuration. -# -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files +# GTNET-SKT test configuration # # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University @@ -19,7 +15,7 @@ nodes = { format = "gtnet" in = { - address = "134.130.169.31:12002" # Local ip:port, use '*' for random port + address = "134.130.169.31:12002" } out = { address = "134.130.169.98:12001" @@ -27,7 +23,7 @@ nodes = { netem = { enabled = false - delay = 1000000 # In micro seconds! + delay = 1000000 jitter = 300000 distribution = "normal" } @@ -38,7 +34,7 @@ nodes = { format = "gtnet" in = { - address = "192.168.88.128:12004" # Local ip:port, use '*' for random port + address = "192.168.88.128:12004" } out = { address = "192.168.88.129:12001" @@ -48,8 +44,8 @@ nodes = { paths = ( { - in = "node1", # Name of the node we listen to (see above) - out = "node1", # And we loop back to the origin + in = "node1", + out = "node1", hooks = ( { diff --git a/etc/gtnet-skt/test5.conf b/etc/gtnet-skt/test5.conf index ca5ec4d26..f7f942bf5 100644 --- a/etc/gtnet-skt/test5.conf +++ b/etc/gtnet-skt/test5.conf @@ -1,8 +1,4 @@ -# GTNET-SKT test configuration. -# -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files +# GTNET-SKT test configuration # # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University @@ -22,7 +18,8 @@ nodes = { } in = { - address = "134.130.169.31:12002" # Local ip:port, use '*' for random port + # Local ip:port, use '*' for random port + address = "134.130.169.31:12002" } out = { address = "134.130.169.98:12001" @@ -30,7 +27,7 @@ nodes = { netem = { enabled = false - delay = 1000000 # In micro seconds! + delay = 1000000 # In micro seconds! jitter = 300000 distribution = "normal" } @@ -41,7 +38,8 @@ nodes = { format = "gtnet" in = { - address = "192.168.88.128:12004" # Local ip:port, use '*' for random port + # Local ip:port, use '*' for random port + address = "192.168.88.128:12004" } out = { address = "192.168.88.129:12001" @@ -51,8 +49,11 @@ nodes = { paths = ( { - in = "node1" # Name of the node we listen to (see above) - out = "node1" # And we loop back to the origin + # Name of the node we listen to (see above) + in = "node1" + + # And we loop back to the origin + out = "node1" hooks = ( { diff --git a/etc/gtnet-skt/test6_gtsync_compare.conf b/etc/gtnet-skt/test6_gtsync_compare.conf index 42bf98974..a0f789cd7 100644 --- a/etc/gtnet-skt/test6_gtsync_compare.conf +++ b/etc/gtnet-skt/test6_gtsync_compare.conf @@ -1,4 +1,4 @@ -# This is an example for a minimal loopback configuration. +# This is an example for a minimal loopback configuration # # All messages will be sent back to the origin using UDP packets. # @@ -38,7 +38,7 @@ nodes = { } in = { - address = "134.130.169.31:12002" # Local ip:port, use '*' for random port + address = "134.130.169.31:12002" } out = { address = "134.130.169.98:12001" @@ -46,7 +46,7 @@ nodes = { netem = { enabled = false - delay = 1000000 # In micro seconds! + delay = 1000000 jitter = 300000 distribution = "normal" } @@ -59,7 +59,7 @@ nodes = { } in = { - address = "134.130.169.31:12004", # Local ip:port, use '*' for random port + address = "134.130.169.31:12004", } out = { address = "134.130.169.99:12003", @@ -69,8 +69,8 @@ nodes = { paths = ( { - in = "node1" # Name of the node we listen to (see above) - out = "node1" # And we loop back to the origin + in = "node1" + out = "node1" hooks = ( { type = "print" diff --git a/etc/labs/lab10_nodes.conf b/etc/labs/lab10_nodes.conf index 43f78abc0..86fbcb93f 100644 --- a/etc/labs/lab10_nodes.conf +++ b/etc/labs/lab10_nodes.conf @@ -8,10 +8,10 @@ nodes = { rpi-1 = { type = "socket" layer = "udp" - format = "gtnet" # pre-built format to communicate in RTDS GTNET-SKT payload + format = "gtnet" # Pre-built format to communicate in RTDS GTNET-SKT payload in = { - address = "*:12005" # villas node machine IP and port number + address = "*:12005" # VILLASnode machine IP and port number signals = { count = 8 @@ -26,16 +26,16 @@ nodes = { ) }, out = { - address = "192.168.0.5:12005" # remote machine IP and port number + address = "192.168.0.5:12005" # Remote machine IP and port number } }, rpi-2 = { type = "socket" layer = "udp" - format = "gtnet" # pre-built format to communicate in RTDS GTNET-SKT payload + format = "gtnet" # Pre-built format to communicate in RTDS GTNET-SKT payload in = { - address = "*:12006" # villas node machine IP and port number + address = "*:12006" # VILLASnode machine IP and port number signals = { count = 8 @@ -50,7 +50,7 @@ nodes = { ) } out = { - address = "192.168.0.6:12006" # remote machine IP and port number + address = "192.168.0.6:12006" # Remote machine IP and port number } }, rtds-1 = { @@ -59,7 +59,7 @@ nodes = { format = "gtnet" in = { - address = "*:12083" # villas node machine IP and port number + address = "*:12083" # VILLASnode machine IP and port number signals = { count = 8 @@ -74,7 +74,7 @@ nodes = { ) } out = { - address = "192.168.0.4:12083" # remote machine IP and port number + address = "192.168.0.4:12083" # Remote machine IP and port number } } } diff --git a/etc/labs/lab10_path_bidir.conf b/etc/labs/lab10_path_bidir.conf index 5c7863f4f..dfe94504c 100644 --- a/etc/labs/lab10_path_bidir.conf +++ b/etc/labs/lab10_path_bidir.conf @@ -18,8 +18,8 @@ paths = ( # and set reverse = true # Example: # { - # in = [ "rpi-1" ], - # out = [ "rtds-1" ], - # reverse = true + # in = [ "rpi-1" ], + # out = [ "rtds-1" ], + # reverse = true # } ) diff --git a/etc/labs/lab11.conf b/etc/labs/lab11.conf index c5ddc1608..2c74eb15e 100644 --- a/etc/labs/lab11.conf +++ b/etc/labs/lab11.conf @@ -71,7 +71,7 @@ paths = ( reverse = false, # The mode of a path determines when the path is triggered - # and forwarding samples to its destination nodes. + # and forwarding samples to its destination nodes mode = "any", # List of nodes which trigger the path diff --git a/etc/labs/lab17.conf b/etc/labs/lab17.conf index bd5cc2d1a..eaed0139a 100644 --- a/etc/labs/lab17.conf +++ b/etc/labs/lab17.conf @@ -14,31 +14,31 @@ nodes = { address = "134.130.169.31:12000" signals = ( - { name="trigger", type="integer" }, - { name="if1_tx_phA_dp0_mag", type="float" }, - { name="if1_tx_phA_dp0_phase", type="float" }, - { name="if1_tx_phA_dp1_mag", type="float" }, - { name="if1_tx_phA_dp1_phase", type="float" }, - { name="if1_tx_phA_dp2_mag", type="float" }, - { name="if1_tx_phA_dp2_phase", type="float" }, - { name="if1_tx_phA_dp3_mag", type="float" }, - { name="if1_tx_phA_dp3_phase", type="float" }, - { name="if1_tx_phB_dp0_mag", type="float" }, - { name="if1_tx_phB_dp0_phase", type="float" }, - { name="if1_tx_phB_dp1_mag", type="float" }, - { name="if1_tx_phB_dp1_phase", type="float" }, - { name="if1_tx_phB_dp2_mag", type="float" }, - { name="if1_tx_phB_dp2_phase", type="float" }, - { name="if1_tx_phB_dp3_mag", type="float" }, - { name="if1_tx_phB_dp3_phase", type="float" }, - { name="if1_tx_phC_dp0_mag", type="float" }, - { name="if1_tx_phC_dp0_phase", type="float" }, - { name="if1_tx_phC_dp1_mag", type="float" }, - { name="if1_tx_phC_dp1_phase", type="float" }, - { name="if1_tx_phC_dp2_mag", type="float" }, - { name="if1_tx_phC_dp2_phase", type="float" }, - { name="if1_tx_phC_dp3_mag", type="float" }, - { name="if1_tx_phC_dp3_phase", type="float" } + { name="trigger", type="integer" }, + { name="if1_tx_phA_dp0_mag", type="float" }, + { name="if1_tx_phA_dp0_phase", type="float" }, + { name="if1_tx_phA_dp1_mag", type="float" }, + { name="if1_tx_phA_dp1_phase", type="float" }, + { name="if1_tx_phA_dp2_mag", type="float" }, + { name="if1_tx_phA_dp2_phase", type="float" }, + { name="if1_tx_phA_dp3_mag", type="float" }, + { name="if1_tx_phA_dp3_phase", type="float" }, + { name="if1_tx_phB_dp0_mag", type="float" }, + { name="if1_tx_phB_dp0_phase", type="float" }, + { name="if1_tx_phB_dp1_mag", type="float" }, + { name="if1_tx_phB_dp1_phase", type="float" }, + { name="if1_tx_phB_dp2_mag", type="float" }, + { name="if1_tx_phB_dp2_phase", type="float" }, + { name="if1_tx_phB_dp3_mag", type="float" }, + { name="if1_tx_phB_dp3_phase", type="float" }, + { name="if1_tx_phC_dp0_mag", type="float" }, + { name="if1_tx_phC_dp0_phase", type="float" }, + { name="if1_tx_phC_dp1_mag", type="float" }, + { name="if1_tx_phC_dp1_phase", type="float" }, + { name="if1_tx_phC_dp2_mag", type="float" }, + { name="if1_tx_phC_dp2_phase", type="float" }, + { name="if1_tx_phC_dp3_mag", type="float" }, + { name="if1_tx_phC_dp3_phase", type="float" } ) } @@ -60,33 +60,32 @@ nodes = { address = "134.130.169.31:12001" signals = ( - { name="trigger", type="integer" }, - { name="if1_tx_phA_dp0_mag", type="float" }, - { name="if1_tx_phA_dp0_phase", type="float" }, - { name="if1_tx_phA_dp1_mag", type="float" }, - { name="if1_tx_phA_dp1_phase", type="float" }, - { name="if1_tx_phA_dp2_mag", type="float" }, - { name="if1_tx_phA_dp2_phase", type="float" }, - { name="if1_tx_phA_dp3_mag", type="float" }, - { name="if1_tx_phA_dp3_phase", type="float" }, - { name="if1_tx_phB_dp0_mag", type="float" }, - { name="if1_tx_phB_dp0_phase", type="float" }, - { name="if1_tx_phB_dp1_mag", type="float" }, - { name="if1_tx_phB_dp1_phase", type="float" }, - { name="if1_tx_phB_dp2_mag", type="float" }, - { name="if1_tx_phB_dp2_phase", type="float" }, - { name="if1_tx_phB_dp3_mag", type="float" }, - { name="if1_tx_phB_dp3_phase", type="float" }, - { name="if1_tx_phC_dp0_mag", type="float" }, - { name="if1_tx_phC_dp0_phase", type="float" }, - { name="if1_tx_phC_dp1_mag", type="float" }, - { name="if1_tx_phC_dp1_phase", type="float" }, - { name="if1_tx_phC_dp2_mag", type="float" }, - { name="if1_tx_phC_dp2_phase", type="float" }, - { name="if1_tx_phC_dp3_mag", type="float" }, - { name="if1_tx_phC_dp3_phase", type="float" } + { name="trigger", type="integer" }, + { name="if1_tx_phA_dp0_mag", type="float" }, + { name="if1_tx_phA_dp0_phase", type="float" }, + { name="if1_tx_phA_dp1_mag", type="float" }, + { name="if1_tx_phA_dp1_phase", type="float" }, + { name="if1_tx_phA_dp2_mag", type="float" }, + { name="if1_tx_phA_dp2_phase", type="float" }, + { name="if1_tx_phA_dp3_mag", type="float" }, + { name="if1_tx_phA_dp3_phase", type="float" }, + { name="if1_tx_phB_dp0_mag", type="float" }, + { name="if1_tx_phB_dp0_phase", type="float" }, + { name="if1_tx_phB_dp1_mag", type="float" }, + { name="if1_tx_phB_dp1_phase", type="float" }, + { name="if1_tx_phB_dp2_mag", type="float" }, + { name="if1_tx_phB_dp2_phase", type="float" }, + { name="if1_tx_phB_dp3_mag", type="float" }, + { name="if1_tx_phB_dp3_phase", type="float" }, + { name="if1_tx_phC_dp0_mag", type="float" }, + { name="if1_tx_phC_dp0_phase", type="float" }, + { name="if1_tx_phC_dp1_mag", type="float" }, + { name="if1_tx_phC_dp1_phase", type="float" }, + { name="if1_tx_phC_dp2_mag", type="float" }, + { name="if1_tx_phC_dp2_phase", type="float" }, + { name="if1_tx_phC_dp3_mag", type="float" }, + { name="if1_tx_phC_dp3_phase", type="float" } ) - } out = { @@ -107,15 +106,15 @@ nodes = { address = "134.130.169.31:12002" signals = ( - { name="orgn_V3phRMSintrf", type="float", unit="V" }, - { name="orgn_Pintrf", type="float", unit="W" }, - { name="orgn_Qintrf", type="float", unit="Var" }, - { name="orgn_Sintrf", type="float", unit="VA" }, - { name="if1_V3phRMS", type="float", unit="V" }, - { name="if1_I3phRMS", type="float", unit="A" }, - { name="if1_P", type="float", unit="W" }, - { name="if1_Q", type="float", unit="Var" }, - { name="if1_S", type="float", unit="VA" } + { name="orgn_V3phRMSintrf", type="float", unit="V" }, + { name="orgn_Pintrf", type="float", unit="W" }, + { name="orgn_Qintrf", type="float", unit="Var" }, + { name="orgn_Sintrf", type="float", unit="VA" }, + { name="if1_V3phRMS", type="float", unit="V" }, + { name="if1_I3phRMS", type="float", unit="A" }, + { name="if1_P", type="float", unit="W" }, + { name="if1_Q", type="float", unit="Var" }, + { name="if1_S", type="float", unit="VA" } ) } diff --git a/etc/labs/lab9_netem.conf b/etc/labs/lab9_netem.conf index 5ac34f107..8810ffe69 100644 --- a/etc/labs/lab9_netem.conf +++ b/etc/labs/lab9_netem.conf @@ -19,11 +19,11 @@ nodes = { netem = { enabled = true, - loss = 0, # in % - corrupt = 0, # in % - duplicate = 0, # in % - delay = 100000, # in uS - jitter = 5000, # in uS + loss = 0, # In % + corrupt = 0, # In % + duplicate = 0, # In % + delay = 100000, # In uS + jitter = 5000, # In uS distribution = "normal" } } diff --git a/etc/loopback.conf b/etc/loopback.conf index 3a3140c9c..10eb4aa73 100644 --- a/etc/loopback.conf +++ b/etc/loopback.conf @@ -1,4 +1,4 @@ -# This is an example for a minimal loopback configuration. +# This is an example for a minimal loopback configuration # # All messages will be sent back to the origin using UDP packets. # @@ -21,10 +21,6 @@ # # $ villas pipe etc/loopback.conf node2 # -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files -# # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University # SPDX-License-Identifier: Apache-2.0 @@ -35,14 +31,15 @@ nodes = { layer = "udp" in = { - address = "127.0.0.1:12000" # Local ip:port, use '*' for random port + # Local ip:port, use '*' for random port + address = "127.0.0.1:12000" } out = { address = "127.0.0.1:12001" netem = { enabled = false - delay = 1000000 # In micro seconds! + delay = 1000000 # In micro seconds! jitter = 300000 distribution = "normal" } @@ -52,7 +49,8 @@ nodes = { type = "socket" layer = "udp" in = { - address = "127.0.0.1:12001" # Local ip:port, use '*' for random port + # Local ip:port, use '*' for random port + address = "127.0.0.1:12001" } out = { address = "127.0.0.1:12002" @@ -62,7 +60,8 @@ nodes = { type = "socket" layer = "udp" in = { - address = "127.0.0.1:12002" # Local ip:port, use '*' for random port + # Local ip:port, use '*' for random port + address = "127.0.0.1:12002" } out = { address = "127.0.0.1:12000" @@ -72,7 +71,8 @@ nodes = { type = "socket" layer = "udp" in = { - address = "127.0.0.1:12003" # Local ip:port, use '*' for random port + # Local ip:port, use '*' for random port + address = "127.0.0.1:12003" } out = { address = "127.0.0.1:12003" @@ -82,8 +82,11 @@ nodes = { paths = ( { - in = "node1" # Name of the node we listen to (see above) - out = "node2" # And we loop back to the origin + # Name of the node we listen to (see above) + in = "node1" + + # And we loop back to the origin + out = "node2" hooks = ( { diff --git a/etc/shmem_mqtt.conf b/etc/shmem_mqtt.conf index 72e43c4fe..d25682b6b 100644 --- a/etc/shmem_mqtt.conf +++ b/etc/shmem_mqtt.conf @@ -12,7 +12,8 @@ nodes = { enabled = false, type = "shmem", in = { - name = "/dpsim-villas", # Name of shared memory segment for sending side + # Name of shared memory segment for sending side + name = "/dpsim-villas", hooks = ( { type = "stats" } ), @@ -24,15 +25,19 @@ nodes = { } }, out = { - name = "/villas-dpsim" # Name of shared memory segment for receiving side + # Name of shared memory segment for receiving side + name = "/villas-dpsim" signals = { count = 1, type = "complex" } }, - queuelen = 1024, # Length of the queues - polling = true, # We can busy-wait or use pthread condition variables for synchronizations + # Length of the queues + queuelen = 1024, + + # We can busy-wait or use pthread condition variables for synchronizations + polling = true, }, broker = { @@ -64,16 +69,14 @@ paths = ( in = "sig", out = "broker", - # mode: any/all # Condition of which/how many source nodes have to receive # at least one sample for the path to be triggered mode = "any", -# reverse = true + # reverse = true } # ,{ - # in = "nano"; - # out = "dpsim"; - # mode = "any" + # in = "nano"; + # out = "dpsim"; + # mode = "any" # } - ) diff --git a/etc/websocket-client.conf b/etc/websocket-client.conf index 6450308f2..afa13d773 100644 --- a/etc/websocket-client.conf +++ b/etc/websocket-client.conf @@ -1,8 +1,4 @@ -# Example configuration file for VILLASnode. -# -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files +# Example configuration file for VILLASnode websocket node-type # # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University diff --git a/etc/websocket-demo.conf b/etc/websocket-demo.conf index 151c6acd3..de879967f 100644 --- a/etc/websocket-demo.conf +++ b/etc/websocket-demo.conf @@ -1,8 +1,4 @@ -# Example configuration file for VILLASnode. -# -# The syntax of this file is similar to JSON. -# A detailed description of the format can be found here: -# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files +# Example configuration file for VILLASnode websocket node-type # # Author: Steffen Vogel # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University diff --git a/flake.lock b/flake.lock index 61fadbbc5..447a49cc0 100644 --- a/flake.lock +++ b/flake.lock @@ -1,97 +1,23 @@ { "nodes": { - "ethercat": { - "flake": false, - "locked": { - "lastModified": 1694079333, - "narHash": "sha256-6F3zBhnU4CFpiO+Cnbd6ecUuiGH/KUntpPfuSYZ+rAI=", - "owner": "etherlab.org", - "repo": "ethercat", - "rev": "722b2d607c4fc004ebf5204aaede0059c02274f4", - "type": "gitlab" - }, - "original": { - "owner": "etherlab.org", - "ref": "stable-1.5", - "repo": "ethercat", - "type": "gitlab" - } - }, - "lib60870": { - "flake": false, - "locked": { - "lastModified": 1672404819, - "narHash": "sha256-9o+gWQbpCJb+UZzPNmzGqpWD0QbGjg41is/f1POUEQs=", - "owner": "mz-automation", - "repo": "lib60870", - "rev": "53a6b3c1cf3023e51cf81763b1ccf048edcd1c64", - "type": "github" - }, - "original": { - "owner": "mz-automation", - "ref": "v2.3.2", - "repo": "lib60870", - "type": "github" - } - }, - "libdatachannel": { - "flake": false, - "locked": { - "lastModified": 1683797946, - "narHash": "sha256-kSK+5gFMG6tq89R1m08gNBKPdwyR/mLEDhWXQ/uk34o=", - "ref": "refs/tags/v0.18.4", - "rev": "7a5e01071ae635e06f175233abd11d623f09cbb8", - "revCount": 2459, - "submodules": true, - "type": "git", - "url": "https://github.com/paullouisageneau/libdatachannel.git" - }, - "original": { - "ref": "refs/tags/v0.18.4", - "submodules": true, - "type": "git", - "url": "https://github.com/paullouisageneau/libdatachannel.git" - } - }, - "libiec61850": { - "flake": false, - "locked": { - "lastModified": 1647022552, - "narHash": "sha256-1vT0ry6IJqilpM7g9l7fx+ET+Dyo24WAyWqTyPM9nQw=", - "owner": "mz-automation", - "repo": "libiec61850", - "rev": "210cf30897631fe2006ac50483caf8fd616622a2", - "type": "github" - }, - "original": { - "owner": "mz-automation", - "ref": "v1.5.1", - "repo": "libiec61850", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1705957679, - "narHash": "sha256-Q8LJaVZGJ9wo33wBafvZSzapYsjOaNjP/pOnSiKVGHY=", + "lastModified": 1712666087, + "narHash": "sha256-WwjUkWsjlU8iUImbivlYxNyMB1L5YVqE8QotQdL9jWc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9a333eaa80901efe01df07eade2c16d183761fa3", + "rev": "a76c4553d7e741e17f289224eda135423de0491d", "type": "github" }, "original": { "owner": "NixOS", - "ref": "release-23.05", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { - "ethercat": "ethercat", - "lib60870": "lib60870", - "libdatachannel": "libdatachannel", - "libiec61850": "libiec61850", "nixpkgs": "nixpkgs" } } diff --git a/flake.nix b/flake.nix index 4a334100e..602a3481f 100644 --- a/flake.nix +++ b/flake.nix @@ -4,209 +4,156 @@ description = "VILLASnode is a client/server application to connect simulation equipment and software."; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/release-23.05"; - - ethercat = { - url = "gitlab:etherlab.org/ethercat/stable-1.5"; - flake = false; - }; - - lib60870 = { - url = "github:mz-automation/lib60870/v2.3.2"; - flake = false; - }; - - libdatachannel = { - type = "git"; - url = "https://github.com/paullouisageneau/libdatachannel.git"; - ref = "refs/tags/v0.18.4"; - submodules = true; - flake = false; - }; - - libiec61850 = { - url = "github:mz-automation/libiec61850/v1.5.1"; - flake = false; - }; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; }; - outputs = { - self, - nixpkgs, - ... - } @ inputs: let - inherit (nixpkgs) lib; + outputs = + { self, nixpkgs, ... }: + let + inherit (nixpkgs) lib; - nixDir = ./packaging/nix; + nixDir = ./packaging/nix; - # Add separateDebugInfo to a derivation - addSeparateDebugInfo = d: - d.overrideAttrs { - separateDebugInfo = true; - }; + # Add separateDebugInfo to a derivation + addSeparateDebugInfo = d: d.overrideAttrs { separateDebugInfo = true; }; - # Supported systems for native compilation - supportedSystems = ["x86_64-linux" "aarch64-linux"]; + # Supported systems for native compilation + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + ]; - # Supported systems to cross compile to - supportedCrossSystems = ["aarch64-multiplatform"]; + # Generate attributes corresponding to all the supported systems + forSupportedSystems = lib.genAttrs supportedSystems; - # Generate attributes corresponding to all the supported systems - forSupportedSystems = lib.genAttrs supportedSystems; - - # Generate attributes corresponding to all supported combinations of system and crossSystem - forSupportedCrossSystems = f: forSupportedSystems (system: lib.genAttrs supportedCrossSystems (f system)); - - # Initialize nixpkgs for the specified `system` - pkgsFor = system: - import nixpkgs { - inherit system; - overlays = with self.overlays; [default]; - }; - - # Initialize nixpkgs for cross-compiling from `system` to `crossSystem` - crossPkgsFor = system: crossSystem: - (import nixpkgs { - inherit system; - overlays = with self.overlays; [ - default - minimal - ]; - }) - .pkgsCross - .${crossSystem}; - - # Initialize development nixpkgs for the specified `system` - devPkgsFor = system: - import nixpkgs { - inherit system; - overlays = with self.overlays; [default debug]; - }; - - # Build villas and its dependencies for the specified `pkgs` - packagesWith = pkgs: rec { - default = villas; - - villas-python = pkgs.callPackage (nixDir + "/python.nix") { - src = ./python; - }; - - villas-minimal = pkgs.callPackage (nixDir + "/villas.nix") { - src = ./.; - version = "minimal"; - }; - - villas = villas-minimal.override { - version = "full"; - withAllExtras = true; - withAllFormats = true; - withAllHooks = true; - withAllNodes = true; - }; - - ethercat = pkgs.callPackage (nixDir + "/ethercat.nix") { - src = inputs.ethercat; - }; - - lib60870 = pkgs.callPackage (nixDir + "/lib60870.nix") { - src = inputs.lib60870; - }; - - libdatachannel = pkgs.callPackage (nixDir + "/libdatachannel.nix") { - src = inputs.libdatachannel; - }; - - libiec61850 = pkgs.callPackage (nixDir + "/libiec61850.nix") { - src = inputs.libiec61850; - }; - }; - in { - # Standard flake attribute for normal packages (not cross-compiled) - packages = forSupportedSystems ( - system: - packagesWith (pkgsFor system) - ); - - # Non-standard attribute for cross-compilated packages - crossPackages = forSupportedCrossSystems ( - system: crossSystem: - packagesWith (crossPkgsFor system crossSystem) - ); - - # Standard flake attribute allowing you to add the villas packages to your nixpkgs - overlays = { - default = final: prev: packagesWith final; - debug = final: prev: { - jansson = addSeparateDebugInfo prev.jansson; - libmodbus = addSeparateDebugInfo prev.libmodbus; - }; - minimal = final: prev: { - mosquitto = prev.mosquitto.override {systemd = final.systemdMinimal;}; - rdma-core = prev.rdma-core.override {udev = final.systemdMinimal;}; - }; - }; - - # Standard flake attribute for defining developer environments - devShells = forSupportedSystems ( - system: let - pkgs = devPkgsFor system; - shellHook = ''[ -z "$PS1" ] || exec "$SHELL"''; - hardeningDisable = ["all"]; - packages = with pkgs; [ - bashInteractive - bc - boxfort - clang-tools - criterion - jq - libffi - libgit2 - pcre - reuse - cppcheck - ]; - in rec { - default = full; - - minimal = pkgs.mkShell { - inherit shellHook hardeningDisable packages; - name = "minimal"; - inputsFrom = with pkgs; [villas-minimal]; + # Initialize nixpkgs for the specified `system` + pkgsFor = + system: + import nixpkgs { + inherit system; + overlays = with self.overlays; [ default ]; }; - full = pkgs.mkShell { - inherit shellHook hardeningDisable packages; - name = "full"; - inputsFrom = with pkgs; [villas]; + # Initialize development nixpkgs for the specified `system` + devPkgsFor = + system: + import nixpkgs { + inherit system; + overlays = with self.overlays; [ + default + debug + ]; }; - } - ); - # Standard flake attribute to add additional checks to `nix flake check` - checks = forSupportedSystems ( - system: let - pkgs = pkgsFor system; - in { - fmt = pkgs.runCommand "check-fmt" {} '' - cd ${self} - "${pkgs.alejandra}/bin/alejandra" --check . 2>> $out - ''; - } - ); + # Build villas and its dependencies for the specified `pkgs` + packagesWith = pkgs: rec { + default = villas-node; - # Standard flake attribute specifying the formatter invoked on `nix fmt` - formatter = forSupportedSystems (system: (pkgsFor system).alejandra); + villas-node-python = pkgs.callPackage (nixDir + "/python.nix") { src = ./.; }; - # Standard flake attribute for NixOS modules - nixosModules = rec { - default = villas; + villas-node-minimal = pkgs.callPackage (nixDir + "/villas.nix") { + src = ./.; + version = "minimal"; + }; - villas = { - imports = [(nixDir + "/module.nix")]; - nixpkgs.overlays = [ - self.overlays.default - ]; + villas-node = villas-node-minimal.override { + version = "full"; + withAllExtras = true; + withAllFormats = true; + withAllHooks = true; + withAllNodes = true; + }; + }; + in + { + # Standard flake attribute for normal packages (not cross-compiled) + packages = forSupportedSystems (system: packagesWith (pkgsFor system)); + + # Standard flake attribute allowing you to add the villas packages to your nixpkgs + overlays = { + default = final: prev: packagesWith final; + debug = final: prev: { + jansson = addSeparateDebugInfo prev.jansson; + libmodbus = addSeparateDebugInfo prev.libmodbus; + }; + minimal = final: prev: { + mosquitto = prev.mosquitto.override { systemd = final.systemdMinimal; }; + rdma-core = prev.rdma-core.override { udev = final.systemdMinimal; }; + }; + }; + + # Standard flake attribute for defining developer environments + devShells = forSupportedSystems ( + system: + let + pkgs = devPkgsFor system; + shellHook = ''[ -z "$PS1" ] || exec "$SHELL"''; + hardeningDisable = [ "all" ]; + packages = with pkgs; [ + bashInteractive + bc + boxfort + clang-tools + criterion + jq + libffi + libgit2 + pcre + reuse + cppcheck + ]; + in + rec { + default = full; + + full = pkgs.mkShell { + inherit shellHook hardeningDisable packages; + name = "full"; + inputsFrom = with pkgs; [ villas-node ]; + }; + + python = pkgs.mkShell { + inherit shellHook hardeningDisable; + name = "python"; + inputsFrom = with pkgs; [ villas-node-python ]; + packages = + with pkgs; + packages + ++ [ + (python3.withPackages (python-pkgs: [ + python-pkgs.build + python-pkgs.twine + ])) + ]; + }; + } + ); + + # Standard flake attribute to add additional checks to `nix flake check` + checks = forSupportedSystems ( + system: + let + pkgs = pkgsFor system; + in + { + fmt = pkgs.runCommand "check-fmt" { } '' + cd ${self} + "${pkgs.nixfmt}/bin/nixfmt" --check . 2>> $out + ''; + } + ); + + # Standard flake attribute specifying the formatter invoked on `nix fmt` + formatter = forSupportedSystems (system: (pkgsFor system).alejandra); + + # Standard flake attribute for NixOS modules + nixosModules = rec { + default = villas; + + villas = { + imports = [ (nixDir + "/module.nix") ]; + nixpkgs.overlays = [ self.overlays.default ]; + }; }; }; - }; } diff --git a/fpga/include/villas/fpga/ips/i2c.hpp b/fpga/include/villas/fpga/ips/i2c.hpp index 73c9fc693..800ea7ce8 100644 --- a/fpga/include/villas/fpga/ips/i2c.hpp +++ b/fpga/include/villas/fpga/ips/i2c.hpp @@ -106,7 +106,7 @@ class I2cFactory : NodeFactory { public: virtual std::string getName() const { return "i2c"; } - virtual std::string getDescription() const { return "Xilinx's AXI4 iic IP"; } + virtual std::string getDescription() const { return "Xilinx's AXI4 IIC IP"; } private: virtual Vlnv getCompatibleVlnv() const { diff --git a/fpga/lib/dma.cpp b/fpga/lib/dma.cpp index 5938d5b4b..0afebfdc2 100644 --- a/fpga/lib/dma.cpp +++ b/fpga/lib/dma.cpp @@ -7,9 +7,6 @@ #include -#include -#include -#include #include #include diff --git a/fpga/lib/utils.cpp b/fpga/lib/utils.cpp index 6963672cc..cc0d3986f 100644 --- a/fpga/lib/utils.cpp +++ b/fpga/lib/utils.cpp @@ -6,7 +6,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include #include #include #include @@ -15,7 +14,6 @@ #include #include -#include #include #include diff --git a/fpga/src/villas-fpga-ctrl.cpp b/fpga/src/villas-fpga-ctrl.cpp index ad803a06e..b2363e401 100644 --- a/fpga/src/villas-fpga-ctrl.cpp +++ b/fpga/src/villas-fpga-ctrl.cpp @@ -8,7 +8,6 @@ */ #include -#include #include #include #include diff --git a/fpga/src/villas-fpga-pipe.cpp b/fpga/src/villas-fpga-pipe.cpp index 0e8543e19..edb8ce9e5 100644 --- a/fpga/src/villas-fpga-pipe.cpp +++ b/fpga/src/villas-fpga-pipe.cpp @@ -5,8 +5,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include -#include #include #include #include diff --git a/fpga/thirdparty/CLI11/CLI11.hpp b/fpga/thirdparty/CLI11/CLI11.hpp index c439fd0d9..382620f73 100644 --- a/fpga/thirdparty/CLI11/CLI11.hpp +++ b/fpga/thirdparty/CLI11/CLI11.hpp @@ -1,303 +1,1276 @@ -/** CLI11 is a command line parser for C++11. - * - * This file was generated using MakeSingleHeader.py in CLI11/scripts from: v1.4.0 - * This has the complete CLI library in one file. - * See https://github.com/CLIUtils/CLI11 for details - * - * SPDX-FileCopyrightText: 2017 University of Cincinnati, developed by Henry Schreiner under NSF AWARD 141473 - * SPDX-License-Identifier: BSD-3-Clause - *********************************************************************************/ +// CLI11: Version 2.4.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: v2.4.1 +// +// SPDX-FileCopyrightText: 2017-2024 University of Cincinnati, developed by Henry, Schreiner under NSF AWARD 1414736. All rights reserved. +// SPDX-License-Identifier: BSD-3-Clause +// +// 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. #pragma once +// 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 #include #include -#include -#include #include #include #include #include -// From CLI/Version.hpp -namespace CLI { - -// Note that all code in CLI11 must be in a namespace, even if it just a define. - -#define CLI11_VERSION_MAJOR 1 +#define CLI11_VERSION_MAJOR 2 #define CLI11_VERSION_MINOR 4 -#define CLI11_VERSION_PATCH 0 -#define CLI11_VERSION "1.4.0" +#define CLI11_VERSION_PATCH 1 +#define CLI11_VERSION "2.4.1" -} // namespace CLI -// From CLI/StringTools.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 + +// GCC < 10 doesn't ignore this in unevaluated contexts +#if !defined(CLI11_CPP17) || \ + (defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4) +#define CLI11_NODISCARD +#else +#define CLI11_NODISCARD [[nodiscard]] +#endif + +/** detection of rtti */ +#ifndef CLI11_USE_STATIC_RTTI +#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#define CLI11_USE_STATIC_RTTI 1 +#elif defined(__cpp_rtti) +#if(defined(_CPPRTTI) && _CPPRTTI == 0) +#define CLI11_USE_STATIC_RTTI 1 +#else +#define CLI11_USE_STATIC_RTTI 0 +#endif +#elif(defined(__GCC_RTTI) && __GXX_RTTI) +#define CLI11_USE_STATIC_RTTI 0 +#else +#define CLI11_USE_STATIC_RTTI 1 +#endif +#endif + +/** availability */ +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 +#elif defined(__wasi__) +// As of wasi-sdk-14, filesystem is not implemented +#define CLI11_HAS_FILESYSTEM 0 +#else +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 +#define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 +#endif +#else +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif +#endif +#endif + +/** availability */ +#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 +#define CLI11_HAS_CODECVT 0 +#else +#define CLI11_HAS_CODECVT 1 +#include +#endif + +/** disable deprecations */ +#if defined(__GNUC__) // GCC or clang +#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") +#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + +#elif defined(_MSC_VER) +#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push)) +#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) + +#else +#define CLI11_DIAGNOSTIC_PUSH +#define CLI11_DIAGNOSTIC_POP + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +#endif + +/** Inline macro **/ +#ifdef CLI11_COMPILE +#define CLI11_INLINE +#else +#define CLI11_INLINE inline +#endif + + + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include // NOLINT(build/include) +#else +#include +#include +#endif + + + + +#ifdef CLI11_CPP17 +#include +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include +#include // NOLINT(build/include) +#endif // CLI11_HAS_FILESYSTEM + + + +#if defined(_WIN32) +#if !(defined(_AMD64_) || defined(_X86_) || defined(_ARM_)) +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \ + defined(_M_AMD64) +#define _AMD64_ +#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(__i386__) || defined(_M_IX86) +#define _X86_ +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT) +#define _ARM_ +#elif defined(__aarch64__) || defined(_M_ARM64) +#define _ARM64_ +#elif defined(_M_ARM64EC) +#define _ARM64EC_ +#endif +#endif + +// first +#ifndef NOMINMAX +// if NOMINMAX is already defined we don't want to mess with that either way +#define NOMINMAX +#include +#undef NOMINMAX +#else +#include +#endif + +// second +#include +// third +#include +#include +#endif + namespace CLI { + + +/// Convert a wide string to a narrow string. +CLI11_INLINE std::string narrow(const std::wstring &str); +CLI11_INLINE std::string narrow(const wchar_t *str); +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size); + +/// Convert a narrow string to a wide string. +CLI11_INLINE std::wstring widen(const std::string &str); +CLI11_INLINE std::wstring widen(const char *str); +CLI11_INLINE std::wstring widen(const char *str, std::size_t size); + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str); +CLI11_INLINE std::wstring widen(std::string_view str); +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// Convert a char-string to a native path correctly. +CLI11_INLINE std::filesystem::path to_path(std::string_view str); +#endif // CLI11_HAS_FILESYSTEM + + + + namespace detail { +#if !CLI11_HAS_CODECVT +/// Attempt to set one of the acceptable unicode locales for conversion +CLI11_INLINE void set_unicode_locale() { + static const std::array unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; + + for(const auto &locale_name : unicode_locales) { + if(std::setlocale(LC_ALL, locale_name) != nullptr) { + return; + } + } + throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8"); +} + +template struct scope_guard_t { + F closure; + + explicit scope_guard_t(F closure_) : closure(closure_) {} + ~scope_guard_t() { closure(); } +}; + +template CLI11_NODISCARD CLI11_INLINE scope_guard_t scope_guard(F &&closure) { + return scope_guard_t{std::forward(closure)}; +} + +#endif // !CLI11_HAS_CODECVT + +CLI11_DIAGNOSTIC_PUSH +CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().to_bytes(str, str + str_size); + +#else + return std::wstring_convert>().to_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const wchar_t *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " + + std::to_string(it - str)); + } + std::string result(new_size, '\0'); + std::wcsrtombs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().from_bytes(str, str + str_size); + +#else + return std::wstring_convert>().from_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const char *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " + + std::to_string(it - str)); + } + std::wstring result(new_size, L'\0'); + std::mbsrtowcs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_DIAGNOSTIC_POP + +} // namespace detail + +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); } +CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); } + +CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); } +CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); } + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); } +CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); } +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE std::filesystem::path to_path(std::string_view str) { + return std::filesystem::path{ +#ifdef _WIN32 + widen(str) +#else + str +#endif // _WIN32 + }; +} +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { +#ifdef _WIN32 +/// Decode and return UTF-8 argv from GetCommandLineW. +CLI11_INLINE std::vector compute_win32_argv(); +#endif +} // namespace detail + + + +namespace detail { + +#ifdef _WIN32 +CLI11_INLINE std::vector compute_win32_argv() { + std::vector result; + int argc = 0; + + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; + // NOLINTBEGIN(*-avoid-c-arrays) + auto wargv = std::unique_ptr(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); + // NOLINTEND(*-avoid-c-arrays) + + if(wargv == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); + } + + result.reserve(static_cast(argc)); + for(size_t i = 0; i < static_cast(argc); ++i) { + result.push_back(narrow(wargv[i])); + } + + return result; +} +#endif + +} // namespace detail + + + + +/// Include the items in this namespace to get free conversion of enums to/from streams. +/// (This is available inside CLI as well, so CLI11 will use this without a using statement). +namespace enums { + +/// output streaming for enumerations +template ::value>::type> +std::ostream &operator<<(std::ostream &in, const T &item) { + // make sure this is out of the detail namespace otherwise it won't be found when needed + return in << static_cast::type>(item); +} + +} // namespace enums + +/// Export to CLI namespace +using enums::operator<<; + +namespace detail { +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not +/// produce overflow for some expected uses +constexpr int expected_max_vector_size{1 << 29}; // 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; -} +CLI11_INLINE std::vector split(const std::string &s, char delim); /// 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(); + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + if(beg != end) + s << *beg++; + while(beg != end) { + s << delim << *beg++; + } + return s.str(); +} + +/// Simple function to join a string from processed elements +template ::value>::type> +std::string join(const T &v, Callable func, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + auto loc = s.tellp(); + while(beg != end) { + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); + } + 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(); + std::ostringstream s; + for(std::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; -} +CLI11_INLINE std::string <rim(std::string &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; -} +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter); /// 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; -} +CLI11_INLINE std::string &rtrim(std::string &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; -} +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter); /// 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); -} +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); + 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 void format_help(std::stringstream &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 << std::endl << std::setw(static_cast(wid)) << ""; - out << description; - } - out << std::endl; -} +/// remove quotes at the front and back of a string either '"' or '\'' +CLI11_INLINE std::string &remove_quotes(std::string &str); -/// 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; -} - -/// Split a string '"one two" "three"' into 'one two', 'three' -inline std::vector split_up(std::string str) { - - std::vector delims = {'\'', '\"'}; - auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; - trim(str); - - std::vector output; - - while (!str.empty()) { - if (str[0] == '\'') { - auto end = str.find('\'', 1); - 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 if (str[0] == '\"') { - auto end = str.find('\"', 1); - 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 = ""; - } - } - trim(str); - } - - return output; -} +/// remove quotes from all elements of a string vector and process escaped components +CLI11_INLINE void remove_quotes(std::vector &args); /// 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; +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input); + +/// 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 +CLI11_INLINE std::ostream & +format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid); + +/// Print subcommand aliases +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid); + +/// Verify the first character of an option +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with +template bool valid_first_char(T c) { + return ((c != '-') && (static_cast(c) > 33)); // space and '!' not allowed } -} // namespace detail -} // namespace CLI +/// Verify following characters of an option +template bool valid_later_char(T c) { + // = and : are value separators, { has special meaning for option defaults, + // and control codes other than tab would just be annoying to deal with in many places allowing space here has too + // much potential for inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && ((static_cast(c) > 32) || c == '\t')); +} -// From CLI/Error.hpp +/// Verify an option/subcommand name +CLI11_INLINE bool valid_name_string(const std::string &str); -namespace CLI { +/// Verify an app name +inline bool valid_alias_name_string(const std::string &str) { + static const std::string badChars(std::string("\n") + '\0'); + return (str.find_first_of(badChars) == std::string::npos); +} -// Use one of these on all error classes -#define CLI11_ERROR_DEF(parent, name) \ -protected: \ - name(std::string name, std::string msg, int exit_code) \ - : parent(std::move(name), std::move(msg), exit_code) {} \ - name(std::string name, std::string msg, ExitCodes exit_code) \ - : parent(std::move(name), 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) {} +/// check if a string is a container segment separator (empty or "%%") +inline bool is_separator(const std::string &str) { + static const std::string sep("%%"); + return (str.empty() || str == sep); +} + +/// Verify that str consists of letters only +inline bool isalpha(const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); +} + +/// 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 +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to); + +/// check if the flag definitions has possible false flags +inline bool has_default_flag_values(const std::string &flags) { + return (flags.find_first_of("{!") != std::string::npos); +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags); + +/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores +CLI11_INLINE std::ptrdiff_t find_member(std::string name, + const std::vector names, + bool ignore_case = false, + bool ignore_underscore = false); + +/// 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) { + std::size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// close a sequence of characters indicated by a closure character. Brackets allows sub sequences +/// recognized bracket sequences include "'`[(<{ other closure characters are assumed to be literal strings +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char); + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket +CLI11_INLINE std::vector split_up(std::string str, char delimiter = '\0'); + +/// get the value of an environmental variable or empty string if empty +CLI11_INLINE std::string get_environment_value(const std::string &env_name); + +/// 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. +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset); + +/// @brief detect if a string has escapable characters +/// @param str the string to do the detection on +/// @return true if the string has escapable characters +CLI11_INLINE bool has_escapable_character(const std::string &str); + +/// @brief escape all escapable characters +/// @param str the string to escape +/// @return a string with the escapble characters escaped with '\' +CLI11_INLINE std::string add_escaped_characters(const std::string &str); + +/// @brief replace the escaped characters with their equivalent +CLI11_INLINE std::string remove_escaped_characters(const std::string &str); + +/// generate a string with all non printable characters escaped to hex codes +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape); + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); + +/// extract an escaped binary_string +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string); + +/// process a quoted string, remove the quotes and if appropriate handle escaped characters +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char = '\"', char literal_char = '\''); + +} // namespace detail + + + + +namespace detail { +CLI11_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; +} + +CLI11_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; +} + +CLI11_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; +} + +CLI11_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; +} + +CLI11_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; +} + +CLI11_INLINE std::string &remove_quotes(std::string &str) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string &remove_outer(std::string &str, char key) { + if(str.length() > 1 && (str.front() == key)) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string fix_newlines(const 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; +} + +CLI11_INLINE std::ostream & +format_help(std::ostream &out, std::string name, const std::string &description, std::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)) << ""; + for(const char c : description) { + out.put(c); + if(c == '\n') { + out << std::setw(static_cast(wid)) << ""; + } + } + } + out << "\n"; + return out; +} + +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << detail::fix_newlines(" ", alias); + } + out << "\n"; + } + return out; +} + +CLI11_INLINE bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) { + return false; + } + auto e = str.end(); + for(auto c = str.begin() + 1; c != e; ++c) + if(!valid_later_char(*c)) + return false; + return true; +} + +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) { + + std::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; +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags) { + auto loc = flags.find_first_of('{', 2); + while(loc != std::string::npos) { + auto finish = flags.find_first_of("},", loc + 1); + if((finish != std::string::npos) && (flags[finish] == '}')) { + flags.erase(flags.begin() + static_cast(loc), + flags.begin() + static_cast(finish) + 1); + } + loc = flags.find_first_of('{', loc + 1); + } + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); +} + +CLI11_INLINE std::ptrdiff_t +find_member(std::string name, const std::vector names, bool ignore_case, bool ignore_underscore) { + auto it = std::end(names); + if(ignore_case) { + if(ignore_underscore) { + name = detail::to_lower(detail::remove_underscore(name)); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(detail::remove_underscore(local_name)) == name; + }); + } else { + name = detail::to_lower(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(local_name) == name; + }); + } + + } else if(ignore_underscore) { + name = detail::remove_underscore(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::remove_underscore(local_name) == name; + }); + } else { + it = std::find(std::begin(names), std::end(names), name); + } + + return (it != std::end(names)) ? (it - std::begin(names)) : (-1); +} + +static const std::string escapedChars("\b\t\n\f\r\"\\"); +static const std::string escapedCharsCode("btnfr\"\\"); +static const std::string bracketChars{"\"'`[(<{"}; +static const std::string matchBracketChars("\"'`])>}"); + +CLI11_INLINE bool has_escapable_character(const std::string &str) { + return (str.find_first_of(escapedChars) != std::string::npos); +} + +CLI11_INLINE std::string add_escaped_characters(const std::string &str) { + std::string out; + out.reserve(str.size() + 4); + for(char s : str) { + auto sloc = escapedChars.find_first_of(s); + if(sloc != std::string::npos) { + out.push_back('\\'); + out.push_back(escapedCharsCode[sloc]); + } else { + out.push_back(s); + } + } + return out; +} + +CLI11_INLINE std::uint32_t hexConvert(char hc) { + int hcode{0}; + if(hc >= '0' && hc <= '9') { + hcode = (hc - '0'); + } else if(hc >= 'A' && hc <= 'F') { + hcode = (hc - 'A' + 10); + } else if(hc >= 'a' && hc <= 'f') { + hcode = (hc - 'a' + 10); + } else { + hcode = -1; + } + return static_cast(hcode); +} + +CLI11_INLINE char make_char(std::uint32_t code) { return static_cast(static_cast(code)); } + +CLI11_INLINE void append_codepoint(std::string &str, std::uint32_t code) { + if(code < 0x80) { // ascii code equivalent + str.push_back(static_cast(code)); + } else if(code < 0x800) { // \u0080 to \u07FF + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + str.push_back(make_char(0xC0 | code >> 6)); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x10000) { // U+0800...U+FFFF + if(0xD800 <= code && code <= 0xDFFF) { + throw std::invalid_argument("[0xD800, 0xDFFF] are not valid UTF-8."); + } + // 1110yyyy 10yxxxxx 10xxxxxx + str.push_back(make_char(0xE0 | code >> 12)); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x110000) { // U+010000 ... U+10FFFF + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + str.push_back(make_char(0xF0 | code >> 18)); + str.push_back(make_char(0x80 | (code >> 12 & 0x3F))); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } +} + +CLI11_INLINE std::string remove_escaped_characters(const std::string &str) { + + std::string out; + out.reserve(str.size()); + for(auto loc = str.begin(); loc < str.end(); ++loc) { + if(*loc == '\\') { + if(str.end() - loc < 2) { + throw std::invalid_argument("invalid escape sequence " + str); + } + auto ecloc = escapedCharsCode.find_first_of(*(loc + 1)); + if(ecloc != std::string::npos) { + out.push_back(escapedChars[ecloc]); + ++loc; + } else if(*(loc + 1) == 'u') { + // must have 4 hex characters + if(str.end() - loc < 6) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16}; + for(int ii = 2; ii < 6; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 5; + } else if(*(loc + 1) == 'U') { + // must have 8 hex characters + if(str.end() - loc < 10) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16 * 16 * 16 * 16 * 16}; + for(int ii = 2; ii < 10; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 9; + } else if(*(loc + 1) == '0') { + out.push_back('\0'); + ++loc; + } else { + throw std::invalid_argument(std::string("unrecognized escape sequence \\") + *(loc + 1) + " in " + str); + } + } else { + out.push_back(*loc); + } + } + return out; +} + +CLI11_INLINE std::size_t close_string_quote(const std::string &str, std::size_t start, char closure_char) { + std::size_t loc{0}; + for(loc = start + 1; loc < str.size(); ++loc) { + if(str[loc] == closure_char) { + break; + } + if(str[loc] == '\\') { + // skip the next character for escaped sequences + ++loc; + } + } + return loc; +} + +CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t start, char closure_char) { + auto loc = str.find_first_of(closure_char, start + 1); + return (loc != std::string::npos ? loc : str.size()); +} + +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) { + + auto bracket_loc = matchBracketChars.find(closure_char); + switch(bracket_loc) { + case 0: + return close_string_quote(str, start, closure_char); + case 1: + case 2: + case std::string::npos: + return close_literal_quote(str, start, closure_char); + default: + break; + } + + std::string closures(1, closure_char); + auto loc = start + 1; + + while(loc < str.size()) { + if(str[loc] == closures.back()) { + closures.pop_back(); + if(closures.empty()) { + return loc; + } + } + bracket_loc = bracketChars.find(str[loc]); + if(bracket_loc != std::string::npos) { + switch(bracket_loc) { + case 0: + loc = close_string_quote(str, loc, str[loc]); + break; + case 1: + case 2: + loc = close_literal_quote(str, loc, str[loc]); + break; + default: + closures.push_back(matchBracketChars[bracket_loc]); + break; + } + } + ++loc; + } + if(loc > str.size()) { + loc = str.size(); + } + return loc; +} + +CLI11_INLINE std::vector split_up(std::string str, char delimiter) { + + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? std::isspace(ch, std::locale()) : (ch == delimiter); + }; + trim(str); + + std::vector output; + while(!str.empty()) { + if(bracketChars.find_first_of(str[0]) != std::string::npos) { + auto bracketLoc = bracketChars.find_first_of(str[0]); + auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]); + if(end >= str.size()) { + output.push_back(std::move(str)); + str.clear(); + } else { + output.push_back(str.substr(0, end + 1)); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + } + + } 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 + 1, str.end()); + } else { + output.push_back(str); + str.clear(); + } + } + trim(str); + } + return output; +} + +CLI11_INLINE std::size_t escape_detect(std::string &str, std::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; +} + +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape) { + // s is our escaped output string + std::string escaped_string{}; + // loop through all characters + for(char c : string_to_escape) { + // check if a given character is printable + // the cast is necessary to avoid undefined behaviour + if(isprint(static_cast(c)) == 0) { + std::stringstream stream; + // if the character is not printable + // we'll convert it to a hex string using a stringstream + // note that since char is signed we have to cast it to unsigned first + stream << std::hex << static_cast(static_cast(c)); + std::string code = stream.str(); + escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code; + + } else { + escaped_string.push_back(c); + } + } + if(escaped_string != string_to_escape) { + auto sqLoc = escaped_string.find('\''); + while(sqLoc != std::string::npos) { + escaped_string.replace(sqLoc, sqLoc + 1, "\\x27"); + sqLoc = escaped_string.find('\''); + } + escaped_string.insert(0, "'B\"("); + escaped_string.push_back(')'); + escaped_string.push_back('"'); + escaped_string.push_back('\''); + } + return escaped_string; +} + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) { + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + return true; + } + return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0); +} + +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) { + std::size_t start{0}; + std::size_t tail{0}; + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + start = 3; + tail = 2; + } else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) { + start = 4; + tail = 3; + } + + if(start == 0) { + return escaped_string; + } + std::string outstring; + + outstring.reserve(ssize - start - tail); + std::size_t loc = start; + while(loc < ssize - tail) { + // ssize-2 to skip )" at the end + if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) { + auto c1 = escaped_string[loc + 2]; + auto c2 = escaped_string[loc + 3]; + + std::uint32_t res1 = hexConvert(c1); + std::uint32_t res2 = hexConvert(c2); + if(res1 <= 0x0F && res2 <= 0x0F) { + loc += 4; + outstring.push_back(static_cast(res1 * 16 + res2)); + continue; + } + } + outstring.push_back(escaped_string[loc]); + ++loc; + } + return outstring; +} + +CLI11_INLINE void remove_quotes(std::vector &args) { + for(auto &arg : args) { + if(arg.front() == '\"' && arg.back() == '\"') { + remove_quotes(arg); + // only remove escaped for string arguments not literal strings + arg = remove_escaped_characters(arg); + } else { + remove_quotes(arg); + } + } +} + +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char literal_char) { + if(str.size() <= 1) { + return false; + } + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + return true; + } + if(str.front() == string_char && str.back() == string_char) { + detail::remove_outer(str, string_char); + if(str.find_first_of('\\') != std::string::npos) { + str = detail::remove_escaped_characters(str); + } + return true; + } + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { + detail::remove_outer(str, str.front()); + return true; + } + return false; +} + +std::string get_environment_value(const std::string &env_name) { + char *buffer = nullptr; + std::string ename_string; + +#ifdef _MSC_VER + // Windows version + std::size_t sz = 0; + if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) { + ename_string = std::string(buffer); + free(buffer); + } +#else + // This also works on Windows, but gives a warning + buffer = std::getenv(env_name.c_str()); + if(buffer != nullptr) { + ename_string = std::string(buffer); + } +#endif + return ename_string; +} + +} // namespace detail + + + +// 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) \ - name(std::string msg) : name(#name, msg, ExitCodes::name) {} +#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, - INIError, - InvalidError, - HorribleError, - OptionNotFound, - ArgumentMismatch, - BaseClass = 127 + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 }; // Error definitions @@ -310,292 +1283,299 @@ enum class ExitCodes { /// All errors derive from this one class Error : public std::runtime_error { - int exit_code; - std::string name{"Error"}; + int actual_exit_code; + std::string error_name{"Error"}; -public: - int get_exit_code() const { return exit_code; } + public: + CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; } - std::string get_name() const { return name; } + CLI11_NODISCARD 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), exit_code(exit_code), name(std::move(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)) {} + 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) + 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 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 single value options"); - } + 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); - } + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString MissingDash(std::string name) { + return BadNameString("Long names strings require 2 dashes " + name); + } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString BadPositionalName(std::string name) { + return BadNameString("Invalid positional 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) - 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); - } + 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 {name + " requires " + other, ExitCodes::OptionAlreadyAdded}; + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return {name + " excludes " + other, ExitCodes::OptionAlreadyAdded}; + } }; // Parsing errors /// Anything that can error in Parse class ParseError : public Error { - CLI11_ERROR_DEF(Error, ParseError) + 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) {} + 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) {} +class CallForHelp : public Success { + CLI11_ERROR_DEF(Success, CallForHelp) + CallForHelp() : CallForHelp("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. +/// Usually something like --help-all on command line +class CallForAllHelp : public Success { + CLI11_ERROR_DEF(Success, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// -v or --version on command line +class CallForVersion : public Success { + CLI11_ERROR_DEF(Success, CallForVersion) + CallForVersion() + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. class RuntimeError : public ParseError { - CLI11_ERROR_DEF(ParseError, RuntimeError) - RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} + 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?)"); - } + 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"); - } + 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) - ValidationError(std::string name, std::string msg) - : ValidationError(name + ": " + msg) {} + 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) - 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); - } + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(std::size_t min_subcom) { + if(min_subcom == 1) { + return RequiredError("A subcommand"); + } + return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError}; + } + static RequiredError + Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) { + if((min_option == 1) && (max_option == 1) && (used == 0)) + return RequiredError("Exactly 1 option from [" + option_list + "]"); + if((min_option == 1) && (max_option == 1) && (used > 1)) { + return {"Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) + + " were given", + ExitCodes::RequiredError}; + } + if((min_option == 1) && (used == 0)) + return RequiredError("At least 1 option from [" + option_list + "]"); + if(used < min_option) { + return {"Requires at least " + std::to_string(min_option) + " options used and only " + + std::to_string(used) + "were given from [" + option_list + "]", + ExitCodes::RequiredError}; + } + if(max_option == 1) + return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError}; + + return {"Requires at most " + std::to_string(max_option) + " options be used and " + std::to_string(used) + + "were given from [" + option_list + "]", + 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) {} + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, std::size_t received) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(received)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(received)), + 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"); - } + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } + static ArgumentMismatch FlagOverride(std::string name) { + return ArgumentMismatch(name + " was given a disallowed flag override"); + } + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + + " required for each element"); + } }; /// 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) {} + 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) {} + 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) - 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) {} + 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) {} + ExtrasError(const std::string &name, std::vector args) + : ExtrasError(name, + (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 INIError : public ParseError { - CLI11_ERROR_DEF(ParseError, INIError) - CLI11_ERROR_SIMPLE(INIError) - static INIError Extras(std::string item) { - return INIError("INI was not able to parse " + item); - } - static INIError NotConfigurable(std::string item) { - return INIError(item + - ": This option is not allowed in a configuration 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) - InvalidError(std::string name) - : InvalidError( - name + - ": Too many positional arguments with unlimited expected args", - ExitCodes::InvalidError) {} + 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) + 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) - OptionNotFound(std::string name) - : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} + 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 -// 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; - -template struct is_vector { static const bool value = false; }; - -template struct is_vector> { - static bool const value = true; -}; - -template struct is_bool { static const bool value = false; }; - -template <> struct is_bool { static bool const value = true; }; - +// Utilities for type enabling namespace detail { // Based generally on https://rmf.io/cxx11/almost-static-if /// Simple empty scoped class @@ -603,6 +1583,704 @@ enum class enabler {}; /// An instance to use in EnableIf constexpr enabler dummy = {}; +} // namespace detail + +/// 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/main/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; + +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) +template struct make_void { + using type = void; +}; + +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine +template using void_t = typename make_void::type; + +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine +template using conditional_t = typename std::conditional::type; + +/// Check to see if something is bool (fail check by default) +template struct is_bool : std::false_type {}; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool : std::true_type {}; + +/// Check to see if something is a shared pointer +template struct is_shared_ptr : std::false_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is copyable pointer +template struct is_copyable_ptr { + static bool const value = is_shared_ptr::value || std::is_pointer::value; +}; + +/// This can be specialized to override the type deduction for IsMember. +template struct IsMemberType { + using type = T; +}; + +/// The main custom type needed here is const char * should be a string. +template <> struct IsMemberType { + using type = std::string; +}; + +namespace detail { + +// These are utilities for IsMember and other transforming objects + +/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that +/// pointer_traits be valid. + +/// not a pointer +template struct element_type { + using type = T; +}; + +template struct element_type::value>::type> { + using type = typename std::pointer_traits::element_type; +}; + +/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of +/// the container +template struct element_value_type { + using type = typename element_type::type::value_type; +}; + +/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. +template struct pair_adaptor : std::false_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } +}; + +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). +/// This wraps a mapped container in a few utilities access it in a general way. +template +struct pair_adaptor< + T, + conditional_t, void>> + : std::true_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward(pair_value))) { + return std::get<0>(std::forward(pair_value)); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward(pair_value))) { + return std::get<1>(std::forward(pair_value)); + } +}; + +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be +// suppressed +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +// check for constructibility from a specific type and copy assignable used in the parse detection +template class is_direct_constructible { + template + static auto test(int, std::true_type) -> decltype( +// NVCC warns about narrowing conversions here +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_suppress 2361 +#else +#pragma diag_suppress 2361 +#endif +#endif + TT{std::declval()} +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_default 2361 +#else +#pragma diag_default 2361 +#endif +#endif + , + std::is_move_assignable()); + + template static auto test(int, std::false_type) -> std::false_type; + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; +}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Check for output streamability +// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream + +template class is_ostreamable { + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for input streamability +template class is_istreamable { + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for complex +template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Templated operation to get a value from a stream +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string &istring, T &obj) { + std::istringstream is; + is.str(istring); + is >> obj; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string & /*istring*/, T & /*obj*/) { + return false; +} + +// check to see if an object is a mutable container (fail by default) +template struct is_mutable_container : std::false_type {}; + +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string +template +struct is_mutable_container< + T, + conditional_t().end()), + decltype(std::declval().clear()), + decltype(std::declval().insert(std::declval().end())>(), + std::declval()))>, + void>> : public conditional_t::value || + std::is_constructible::value, + std::false_type, + std::true_type> {}; + +// check to see if an object is a mutable container (fail by default) +template struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from +/// a std::string +template +struct is_readable_container< + T, + conditional_t().end()), decltype(std::declval().begin())>, void>> + : public std::true_type {}; + +// check to see if an object is a wrapper (fail by default) +template struct is_wrapper : std::false_type {}; + +// check if an object is a wrapper (it has a value_type defined) +template +struct is_wrapper, void>> : public std::true_type {}; + +// Check for tuple like types, as in classes with a tuple_size type trait +template class is_tuple_like { + template + // static auto test(int) + // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); + static auto test(int) -> decltype(std::tuple_size::type>::value, std::true_type{}); + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Convert an object to a string (directly forward if this can become a string) +template ::value, detail::enabler> = detail::dummy> +auto to_string(T &&value) -> decltype(std::forward(value)) { + return std::forward(value); +} + +/// Construct a string from the object +template ::value && !std::is_convertible::value, + detail::enabler> = detail::dummy> +std::string to_string(const T &value) { + return std::string(value); // NOLINT(google-readability-casting) +} + +/// Convert an object to a string (streaming must be supported for that type) +template ::value && !std::is_constructible::value && + is_ostreamable::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&value) { + std::stringstream stream; + stream << value; + return stream.str(); +} + +/// If conversion is not supported, return an empty string (streaming is not supported for that type) +template ::value && !is_ostreamable::value && + !is_readable_container::type>::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&) { + return {}; +} + +/// convert a readable container to a string +template ::value && !is_ostreamable::value && + is_readable_container::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&variable) { + auto cval = variable.begin(); + auto end = variable.end(); + if(cval == end) { + return {"{}"}; + } + std::vector defaults; + while(cval != end) { + defaults.emplace_back(CLI::detail::to_string(*cval)); + ++cval; + } + return {"[" + detail::join(defaults) + "]"}; +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +auto checked_to_string(T &&value) -> decltype(to_string(std::forward(value))) { + return to_string(std::forward(value)); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +std::string checked_to_string(T &&) { + return std::string{}; +} +/// get a string as a convertible value for arithmetic types +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(value); +} +/// get a string as a convertible value for enumerations +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(static_cast::type>(value)); +} +/// for other types just use the regular to_string function +template ::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> +auto value_string(const T &value) -> decltype(to_string(value)) { + return to_string(value); +} + +/// template to get the underlying value type if it exists or use a default +template struct wrapped_type { + using type = def; +}; + +/// Type size for regular object types that do not look like a tuple +template struct wrapped_type::value>::type> { + using type = typename T::value_type; +}; + +/// This will only trigger for actual void type +template struct type_count_base { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{std::tuple_size::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template struct type_count_base::value>::type> { + static constexpr int value{type_count_base::value}; +}; + +/// Set of overloads to get the type size of an object + +/// forward declare the subtype_count structure +template struct subtype_count; + +/// forward declare the subtype_count_min structure +template struct subtype_count_min; + +/// This will only trigger for actual void type +template struct type_count { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count::value && !is_tuple_like::value && !is_complex::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count::value>::type> { + static constexpr int value{2}; +}; + +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template struct type_count::value>::type> { + static constexpr int value{subtype_count::value}; +}; + +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) +template +struct type_count::value && !is_complex::value && !is_tuple_like::value && + !is_mutable_container::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size() { + return subtype_count::type>::value + tuple_type_size(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count::value>::type> { + static constexpr int value{tuple_type_size()}; +}; + +/// definition of subtype count +template struct subtype_count { + static constexpr int value{is_mutable_container::value ? expected_max_vector_size : type_count::value}; +}; + +/// This will only trigger for actual void type +template struct type_count_min { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_min< + T, + typename std::enable_if::value && !is_tuple_like::value && !is_wrapper::value && + !is_complex::value && !std::is_void::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count_min::value>::type> { + static constexpr int value{1}; +}; + +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template +struct type_count_min< + T, + typename std::enable_if::value && !is_complex::value && !is_tuple_like::value>::type> { + static constexpr int value{subtype_count_min::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size_min() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size_min() { + return subtype_count_min::type>::value + tuple_type_size_min(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count_min::value>::type> { + static constexpr int value{tuple_type_size_min()}; +}; + +/// definition of subtype count +template struct subtype_count_min { + static constexpr int value{is_mutable_container::value + ? ((type_count::value < expected_max_vector_size) ? type_count::value : 0) + : type_count_min::value}; +}; + +/// This will only trigger for actual void type +template struct expected_count { + static const int value{0}; +}; + +/// For most types the number of expected items is 1 +template +struct expected_count::value && !is_wrapper::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; +/// number of expected items in a vector +template struct expected_count::value>::type> { + static constexpr int value{expected_max_vector_size}; +}; + +/// number of expected items in a vector +template +struct expected_count::value && is_wrapper::value>::type> { + static constexpr int value{expected_count::value}; +}; + +// Enumeration of the different supported categorizations of objects +enum class object_category : int { + char_value = 1, + integral_value = 2, + unsigned_integral = 4, + enumeration = 6, + boolean_value = 8, + floating_point = 10, + number_constructible = 12, + double_constructible = 14, + integer_constructible = 16, + // string like types + string_assignable = 23, + string_constructible = 24, + wstring_assignable = 25, + wstring_constructible = 26, + other = 45, + // special wrapper or container types + wrapper_value = 50, + complex_number = 60, + tuple_value = 70, + container_value = 80, + +}; + +/// Set of overloads to classify an object according to type + +/// some type that is not otherwise recognized +template struct classify_object { + static constexpr object_category value{object_category::other}; +}; + +/// Signed integers +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_same::value && std::is_signed::value && + !is_bool::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::integral_value}; +}; + +/// Unsigned integers +template +struct classify_object::value && std::is_unsigned::value && + !std::is_same::value && !is_bool::value>::type> { + static constexpr object_category value{object_category::unsigned_integral}; +}; + +/// single character values +template +struct classify_object::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::char_value}; +}; + +/// Boolean values +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::boolean_value}; +}; + +/// Floats +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::floating_point}; +}; +#if defined _MSC_VER +// in MSVC wstring should take precedence if available this isn't as useful on other compilers due to the broader use of +// utf-8 encoding +#define WIDE_STRING_CHECK \ + !std::is_assignable::value && !std::is_constructible::value +#define STRING_CHECK true +#else +#define WIDE_STRING_CHECK true +#define STRING_CHECK !std::is_assignable::value && !std::is_constructible::value +#endif + +/// String and similar direct assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && WIDE_STRING_CHECK && + std::is_assignable::value>::type> { + static constexpr object_category value{object_category::string_assignable}; +}; + +/// String and similar constructible and copy assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + WIDE_STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::string_constructible}; +}; + +/// Wide strings +template +struct classify_object::value && !std::is_integral::value && + STRING_CHECK && std::is_assignable::value>::type> { + static constexpr object_category value{object_category::wstring_assignable}; +}; + +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::wstring_constructible}; +}; + +/// Enumerations +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::enumeration}; +}; + +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::complex_number}; +}; + +/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, +/// vectors, and enumerations +template struct uncommon_type { + using type = typename std::conditional< + !std::is_floating_point::value && !std::is_integral::value && + !std::is_assignable::value && !std::is_constructible::value && + !std::is_assignable::value && !std::is_constructible::value && + !is_complex::value && !is_mutable_container::value && !std::is_enum::value, + std::true_type, + std::false_type>::type; + static constexpr bool value = type::value; +}; + +/// wrapper type +template +struct classify_object::value && is_wrapper::value && + !is_tuple_like::value && uncommon_type::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; +}; + +/// Assignable from double or int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::number_constructible}; +}; + +/// Assignable from int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && !is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::integer_constructible}; +}; + +/// Assignable from double +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + !is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::double_constructible}; +}; + +/// Tuple type +template +struct classify_object< + T, + typename std::enable_if::value && + ((type_count::value >= 2 && !is_wrapper::value) || + (uncommon_type::value && !is_direct_constructible::value && + !is_direct_constructible::value) || + (uncommon_type::value && type_count::value >= 2))>::type> { + static constexpr object_category value{object_category::tuple_value}; + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be + // constructed from just the first element so tuples of can be constructed from a string, which + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out + // those cases that are caught by other object classifications +}; + +/// container type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::container_value}; +}; // Type name print @@ -611,2578 +2289,8678 @@ constexpr enabler dummy = {}; /// But this is cleaner and works better in this case template ::value && std::is_signed::value, - detail::enabler> = detail::dummy> + enable_if_t::value == object_category::char_value, detail::enabler> = detail::dummy> constexpr const char *type_name() { - return "INT"; + return "CHAR"; } template ::value && std::is_unsigned::value, + enable_if_t::value == object_category::integral_value || + classify_object::value == object_category::integer_constructible, 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"; + return "INT"; } template ::value && - !std::is_integral::value && !is_vector::value, + enable_if_t::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value == object_category::floating_point || + classify_object::value == object_category::number_constructible || + classify_object::value == object_category::double_constructible, detail::enabler> = detail::dummy> constexpr const char *type_name() { - return "TEXT"; + return "FLOAT"; +} + +/// Print name for enumeration types +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "ENUM"; +} + +/// Print name for enumeration types +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "BOOLEAN"; +} + +/// Print name for enumeration types +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "COMPLEX"; +} + +/// Print for all other types +template ::value >= object_category::string_assignable && + classify_object::value <= object_category::other, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} +/// typename for tuple value +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Generate type name for a wrapper or container value +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Print name for single element tuple types +template ::value == object_category::tuple_value && type_count_base::value == 1, + detail::enabler> = detail::dummy> +inline std::string type_name() { + return type_name::type>::type>(); +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_name() { + return std::string{}; +} + +/// Recursively generate the tuple type name +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_name() { + auto str = std::string{type_name::type>::type>()} + ',' + + tuple_name(); + if(str.back() == ',') + str.pop_back(); + return str; +} + +/// Print type name for tuples with 2 or more elements +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler>> +inline std::string type_name() { + auto tname = std::string(1, '[') + tuple_name(); + tname.push_back(']'); + return tname; +} + +/// get the type name for a type that has a value_type member +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler>> +inline std::string type_name() { + return type_name(); } // Lexical cast -/// Signed integers / enums -template ::value && - std::is_signed::value) || - std::is_enum::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); +/// Convert to an unsigned integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty() || input.front() == '-') { + return false; + } + char *val{nullptr}; + errno = 0; + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } output = static_cast(output_ll); - return n == input.size() && static_cast(output) == output_ll; - } catch (const std::invalid_argument &) { + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + val = nullptr; + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0); + if(val == (input.c_str() + input.size())) { + output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); + return (static_cast(output) == output_sll); + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } 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); +/// Convert to a signed integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + errno = 0; + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } output = static_cast(output_ll); - return n == input.size() && - static_cast(output) == output_ll; - } catch (const std::invalid_argument &) { + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + if(input == "true") { + // this is to deal with a few oddities with flags and wrapper int types + output = static_cast(1); + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } return false; - } catch (const std::out_of_range &) { - return false; - } +} + +/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed +inline std::int64_t to_flag_value(std::string val) noexcept { + static const std::string trueString("true"); + static const std::string falseString("false"); + if(val == trueString) { + return 1; + } + if(val == falseString) { + return -1; + } + val = detail::to_lower(val); + std::int64_t ret = 0; + if(val.size() == 1) { + if(val[0] >= '1' && val[0] <= '9') { + return (static_cast(val[0]) - '0'); + } + switch(val[0]) { + case '0': + case 'f': + case 'n': + case '-': + ret = -1; + break; + case 't': + case 'y': + case '+': + ret = 1; + break; + default: + errno = EINVAL; + return -1; + } + return ret; + } + if(val == trueString || val == "on" || val == "yes" || val == "enable") { + ret = 1; + } else if(val == falseString || val == "off" || val == "no" || val == "disable") { + ret = -1; + } else { + char *loc_ptr{nullptr}; + ret = std::strtoll(val.c_str(), &loc_ptr, 0); + if(loc_ptr != (val.c_str() + val.size()) && errno == 0) { + errno = EINVAL; + } + } + return ret; +} + +/// Integer conversion +template ::value == object_category::integral_value || + classify_object::value == object_category::unsigned_integral, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return integral_conversion(input, output); +} + +/// char values +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.size() == 1) { + output = static_cast(input[0]); + return true; + } + return integral_conversion(input, output); +} + +/// Boolean values +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + errno = 0; + auto out = to_flag_value(input); + if(errno == 0) { + output = (out > 0); + } else if(errno == ERANGE) { + output = (input[0] != '-'); + } else { + return false; + } + return true; } /// 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 &) { +template ::value == object_category::floating_point, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.empty()) { + return false; + } + char *val = nullptr; + auto output_ld = std::strtold(input.c_str(), &val); + output = static_cast(output_ld); + if(val == (input.c_str() + input.size())) { + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return lexical_cast(nstring, output); + } return false; - } catch (const std::out_of_range &) { - return false; - } } -/// String and similar -template < - typename T, - enable_if_t::value && - !std::is_integral::value && !std::is_enum::value && - std::is_assignable::value, - detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { - output = input; - return true; +/// complex +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename wrapped_type::type; + XC x{0.0}, y{0.0}; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of("+-"); + if(nloc != std::string::npos && nloc > 0) { + worked = lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if(str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && lexical_cast(str1, y); + } else { + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = lexical_cast(str1, y); + x = XC{0}; + } else { + worked = lexical_cast(str1, x); + y = XC{0}; + } + } + if(worked) { + output = T{x, y}; + return worked; + } + return from_stream(input, output); } -/// Non-string parsable +/// String and similar direct assignment +template ::value == object_category::string_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = input; + return true; +} + +/// String and similar constructible and copy assignment template < typename T, - enable_if_t::value && - !std::is_integral::value && !std::is_enum::value && - !std::is_assignable::value, - detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { + enable_if_t::value == object_category::string_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T(input); + return true; +} -// On GCC 4.7, thread_local is not available, so this optimization -// is turned off (avoiding multiple initialisations on multiple usages -#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && \ - __GNUC__ == 4 && (__GNUC_MINOR__ < 8) - std::istringstream is; -#else - static thread_local std::istringstream is; +/// Wide strings +template < + typename T, + enable_if_t::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = widen(input); + return true; +} + +template < + typename T, + enable_if_t::value == object_category::wstring_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T{widen(input)}; + return true; +} + +/// Enumerations +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename std::underlying_type::type val; + if(!integral_conversion(input, val)) { + return false; + } + output = static_cast(val); + return true; +} + +/// wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return from_stream(input, output); +} + +template ::value == object_category::wrapper_value && + !std::is_assignable::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Assignable from double or int +template < + typename T, + enable_if_t::value == object_category::number_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + + double dval = 0.0; + if(lexical_cast(input, dval)) { + output = T{dval}; + return true; + } + + return from_stream(input, output); +} + +/// Assignable from int +template < + typename T, + enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + return from_stream(input, output); +} + +/// Assignable from double +template < + typename T, + enable_if_t::value == object_category::double_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + double val = 0.0; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Non-string convertible from an int +template ::value == object_category::other && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) #endif - - is.str(input); - is >> output; - return !is.fail() && !is.rdbuf()->in_avail(); + // with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevant for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP } -} // namespace detail -} // namespace CLI +/// Non-string parsable by a stream +template ::value == object_category::other && !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + static_assert(is_istreamable::value, + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " + "is convertible from another type use the add_option(...) with XC being the known type"); + return from_stream(input, output); +} + +/// Assign a value through lexical cast operations +/// Strings can be empty so we need to do a little different +template ::value && + (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible || + classify_object::value == object_category::wstring_assignable || + classify_object::value == object_category::wstring_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && std::is_assignable::value && + classify_object::value != object_category::string_assignable && + classify_object::value != object_category::string_constructible && + classify_object::value != object_category::wstring_assignable && + classify_object::value != object_category::wstring_constructible, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = AssignTo{}; + return true; + } + + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && !std::is_assignable::value && + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; + } + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template ::value && !std::is_assignable::value && + classify_object::value != object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val{0}; + if(lexical_cast(input, val)) { +#if defined(__clang__) +/* on some older clang compilers */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#endif + output = val; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + return true; + } + return false; +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template ::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; + if(parse_result) { + output = val; + } + return parse_result; +} + +/// Assign a value from a lexical cast through constructing a value and move assigning it +template < + typename AssignTo, + typename ConvertTo, + enable_if_t::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if(parse_result) { + output = AssignTo(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; +} + +/// primary lexical conversion operation, 1 string to 1 type of some kind +template ::value <= object_category::other && + classify_object::value <= object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element +/// constructor +template ::value <= 2) && expected_count::value == 1 && + is_tuple_like::value && type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + // the remove const is to handle pair types coming from a container + using FirstType = typename std::remove_const::type>::type; + using SecondType = typename std::tuple_element<1, ConvertTo>::type; + FirstType v1; + SecondType v2; + bool retval = lexical_assign(strings[0], v1); + retval = retval && lexical_assign((strings.size() > 1) ? strings[1] : std::string{}, v2); + if(retval) { + output = AssignTo{v1, v2}; + } + return retval; +} + +/// Lexical conversion of a container types of single elements +template ::value && is_mutable_container::value && + type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); + if(strings.empty()) { + return true; + } + if(strings.size() == 1 && strings[0] == "{}") { + return true; + } + bool skip_remaining = false; + if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) { + skip_remaining = true; + } + for(const auto &elem : strings) { + typename AssignTo::value_type out; + bool retval = lexical_assign(elem, out); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(out)); + if(skip_remaining) { + break; + } + } + return (!output.empty()); +} + +/// Lexical conversion for complex types +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename wrapped_type::type; + XC2 x{0.0}, y{0.0}; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + } + auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y); + if(worked) { + output = ConvertTo{x, y}; + } + return worked; + } + return lexical_assign(strings[0], output); +} + +/// Conversion to a vector type using a particular single type as the conversion type +template ::value && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + retval = retval && lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; +} + +// forward declaration + +/// Lexical conversion of a container types with conversion type of two elements +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(std::vector strings, AssignTo &output); + +/// Lexical conversion of a vector types with type_size >2 forward declaration +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); + +/// Conversion for tuples +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); // forward declaration + +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large +/// tuple +template ::value && !is_mutable_container::value && + classify_object::value != object_category::wrapper_value && + (is_mutable_container::value || type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { + ConvertTo val; + auto retval = lexical_conversion(strings, val); + output = AssignTo{val}; + return retval; + } + output = AssignTo{}; + return true; +} + +/// function template for converting tuples if the static Index is greater than the tuple size +template +inline typename std::enable_if<(I >= type_count_base::value), bool>::type +tuple_conversion(const std::vector &, AssignTo &) { + return true; +} + +/// Conversion of a tuple element where the type size ==1 and not a mutable container +template +inline typename std::enable_if::value && type_count::value == 1, bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_assign(strings[0], output); + strings.erase(strings.begin()); + return retval; +} + +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container +template +inline typename std::enable_if::value && (type_count::value > 1) && + type_count::value == type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_conversion(strings, output); + strings.erase(strings.begin(), strings.begin() + type_count::value); + return retval; +} + +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes +template +inline typename std::enable_if::value || + type_count::value != type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + + std::size_t index{subtype_count_min::value}; + const std::size_t mx_count{subtype_count::value}; + const std::size_t mx{(std::min)(mx_count, strings.size() - 1)}; + + while(index < mx) { + if(is_separator(strings[index])) { + break; + } + ++index; + } + bool retval = lexical_conversion( + std::vector(strings.begin(), strings.begin() + static_cast(index)), output); + if(strings.size() > index) { + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + } else { + strings.clear(); + } + return retval; +} + +/// Tuple conversion operation +template +inline typename std::enable_if<(I < type_count_base::value), bool>::type +tuple_conversion(std::vector strings, AssignTo &output) { + bool retval = true; + using ConvertToElement = typename std:: + conditional::value, typename std::tuple_element::type, ConvertTo>::type; + if(!strings.empty()) { + retval = retval && tuple_type_conversion::type, ConvertToElement>( + strings, std::get(output)); + } + retval = retval && tuple_conversion(std::move(strings), output); + return retval; +} + +/// Lexical conversion of a container types with tuple elements of size 2 +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler>> +bool lexical_conversion(std::vector strings, AssignTo &output) { + output.clear(); + while(!strings.empty()) { + + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; + bool retval = tuple_type_conversion(strings, v1); + if(!strings.empty()) { + retval = retval && tuple_type_conversion(strings, v2); + } + if(retval) { + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); + } else { + return false; + } + } + return (!output.empty()); +} + +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + static_assert( + !is_tuple_like::value || type_count_base::value == type_count_base::value, + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); + return tuple_conversion(strings, output); +} + +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + std::vector temp; + std::size_t ii{0}; + std::size_t icount{0}; + std::size_t xcm{type_count::value}; + auto ii_max = strings.size(); + while(ii < ii_max) { + temp.push_back(strings[ii]); + ++ii; + ++icount; + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { + if(static_cast(xcm) > type_count_min::value && is_separator(temp.back())) { + temp.pop_back(); + } + typename AssignTo::value_type temp_out; + retval = retval && + lexical_conversion(temp, temp_out); + temp.clear(); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(temp_out)); + icount = 0; + } + } + return retval; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + if(strings.empty() || strings.front().empty()) { + output = ConvertTo{}; + return true; + } + typename ConvertTo::value_type val; + if(lexical_conversion(strings, val)) { + output = ConvertTo{val}; + return true; + } + return false; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion(strings, val)) { + output = val; + return true; + } + return false; +} + +/// Sum a vector of strings +inline std::string sum_string_vector(const std::vector &values) { + double val{0.0}; + bool fail{false}; + std::string output; + for(const auto &arg : values) { + double tv{0.0}; + auto comp = lexical_cast(arg, tv); + if(!comp) { + errno = 0; + auto fv = detail::to_flag_value(arg); + fail = (errno != 0); + if(fail) { + break; + } + tv = static_cast(fv); + } + val += tv; + } + if(fail) { + for(const auto &arg : values) { + output.append(arg); + } + } else { + std::ostringstream out; + out.precision(16); + out << val; + output = out.str(); + } + return output; +} + +} // namespace detail + -// 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; -} +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest); // 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("="); - if (loc != std::string::npos) { - name = current.substr(2, loc - 2); - value = current.substr(loc + 1); - } else { - name = current.substr(2); - value = ""; +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value); + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value); + +// Splits a string into multiple long and short names +CLI11_INLINE std::vector split_names(std::string current); + +/// extract default flag values either {def} or starting with a ! +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str); + +/// Get a vector of short names, one of long names, and a single name +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input); + +} // namespace detail + + + +namespace detail { + +CLI11_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; } - 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; +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.compare(0, 2, "--") == 0 && 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; + } + return false; } -/// Get a vector of short names, one of long names, and a single name -inline std::tuple, std::vector, - std::string> +CLI11_INLINE bool split_windows_style(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; + } + return false; +} + +CLI11_INLINE std::vector split_names(std::string current) { + std::vector output; + std::size_t val = 0; + 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; +} + +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str) { + std::vector flags = split_names(str); + flags.erase(std::remove_if(flags.begin(), + flags.end(), + [](const std::string &name) { + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && + (name.back() == '}')) || + (name[0] == '!')))); + }), + flags.end()); + std::vector> output; + output.reserve(flags.size()); + for(auto &flag : flags) { + auto def_start = flag.find_first_of('{'); + std::string defval = "false"; + if((def_start != std::string::npos) && (flag.back() == '}')) { + defval = flag.substr(def_start + 1); + defval.pop_back(); + flag.erase(def_start, std::string::npos); // NOLINT(readability-suspicious-call-argument) + } + flag.erase(0, flag.find_first_not_of("-!")); + output.emplace_back(flag, defval); + } + return output; +} + +CLI11_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; + std::vector short_names; + std::vector long_names; + std::string pos_name; + for(std::string name : input) { + if(name.length() == 0) { + continue; + } + 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 if(name.length() > 2) + throw BadNameString::MissingDash(name); + 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.empty()) + throw BadNameString::MultiPositionalNames(name); + if(valid_name_string(name)) { + pos_name = name; + } else { + throw BadNameString::BadPositionalName(name); + } + } } - } - - return std::tuple, std::vector, - std::string>(short_names, long_names, pos_name); + return std::make_tuple(short_names, long_names, pos_name); } -} // namespace detail -} // namespace CLI +} // namespace detail -// From CLI/Ini.hpp -namespace CLI { -namespace detail { -inline std::string inijoin(std::vector args) { - std::ostringstream s; - size_t start = 0; - for (const auto &arg : args) { - if (start++ > 0) - s << " "; +class App; - 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"(')"; - } +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents{}; - return s.str(); -} + /// This is the name + std::string name{}; + /// Listing of inputs + std::vector inputs{}; -struct ini_ret_t { - /// This is the full name with dots - std::string fullname; - - /// Listing of inputs - std::vector inputs; - - /// Current parent level - size_t level = 0; - - /// Return parent or empty string, based on level - /// - /// Level 0, a.b.c would return a - /// Level 1, a.b.c could return b - std::string parent() const { - std::vector plist = detail::split(fullname, '.'); - if (plist.size() > (level + 1)) - return plist[level]; - else - return ""; - } - - /// Return name - std::string name() const { - std::vector plist = detail::split(fullname, '.'); - return plist.at(plist.size() - 1); - } + /// The list of parents and name joined by "." + CLI11_NODISCARD std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } }; -/// Internal parsing function -inline std::vector parse_ini(std::istream &input) { - std::string name, line; - std::string section = "default"; +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items{}; - std::vector output; + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; - while (getline(input, line)) { - std::vector items; + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; - 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(); - ini_ret_t &out = output.back(); - - // Find = in string, split and recombine - auto pos = line.find("="); - if (pos != std::string::npos) { - name = detail::trim_copy(line.substr(0, pos)); - std::string item = detail::trim_copy(line.substr(pos + 1)); - items = detail::split_up(item); - } else { - name = detail::trim_copy(line); - items = {"ON"}; - } - - if (detail::to_lower(section) == "default") - out.fullname = name; - else - out.fullname = section + "." + name; - - out.inputs.insert(std::end(out.inputs), std::begin(items), - std::end(items)); + /// Get a flag value + CLI11_NODISCARD virtual std::string to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + return item.inputs.at(0); + } + if(item.inputs.empty()) { + return "{}"; + } + throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE } - } - return output; -} -/// Parse an INI file, throw an error (ParseError:INIParseError or FileError) on failure -inline std::vector parse_ini(const std::string &name) { + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + CLI11_NODISCARD std::vector from_file(const std::string &name) const { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); - std::ifstream input{name}; - if (!input.good()) - throw FileError::Missing(name); + return from_config(input); + } - return parse_ini(input); -} + /// Virtual destructor + virtual ~Config() = default; +}; -} // namespace detail -} // namespace CLI +/// This converter works with INI/TOML files; to write INI files use ConfigINI +class ConfigBase : public Config { + protected: + /// the character used for comments + char commentChar = '#'; + /// the character used to start an array '\0' is a default to not use + char arrayStart = '['; + /// the character used to end an array '\0' is a default to not use + char arrayEnd = ']'; + /// the character used to separate elements in an array + char arraySeparator = ','; + /// the character used separate the name from the value + char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters and literal strings + char literalQuote = '\''; + /// the maximum number of layers to allow + uint8_t maximumLayers{255}; + /// the separator used to separator parent layers + char parentSeparatorChar{'.'}; + /// Specify the configuration index to use for arrayed sections + int16_t configIndex{-1}; + /// Specify the configuration section that should be used + std::string configSection{}; -// From CLI/Validators.hpp + public: + std::string + to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; -namespace CLI { + std::vector from_config(std::istream &input) const override; + /// Specify the configuration for comment characters + ConfigBase *comment(char cchar) { + commentChar = cchar; + return this; + } + /// Specify the start and end characters for an array + ConfigBase *arrayBounds(char aStart, char aEnd) { + arrayStart = aStart; + arrayEnd = aEnd; + return this; + } + /// Specify the delimiter character for an array + ConfigBase *arrayDelimiter(char aSep) { + arraySeparator = aSep; + return this; + } + /// Specify the delimiter between a name and value + ConfigBase *valueSeparator(char vSep) { + valueDelimiter = vSep; + return this; + } + /// Specify the quote characters used around strings and literal strings + ConfigBase *quoteCharacter(char qString, char literalChar) { + stringQuote = qString; + literalQuote = literalChar; + return this; + } + /// Specify the maximum number of parents + ConfigBase *maxLayers(uint8_t layers) { + maximumLayers = layers; + return this; + } + /// Specify the separator to use for parent layers + ConfigBase *parentSeparator(char sep) { + parentSeparatorChar = sep; + return this; + } + /// get a reference to the configuration section + std::string §ionRef() { return configSection; } + /// get the section + CLI11_NODISCARD const std::string §ion() const { return configSection; } + /// specify a particular section of the configuration file to use + ConfigBase *section(const std::string §ionName) { + configSection = sectionName; + return this; + } + + /// get a reference to the configuration index + int16_t &indexRef() { return configIndex; } + /// get the section index + CLI11_NODISCARD int16_t index() const { return configIndex; } + /// specify a particular index in the section to use (-1) for all sections to use + ConfigBase *index(int16_t sectionIndex) { + configIndex = sectionIndex; + return this; + } +}; + +/// the default Config is the TOML file format +using ConfigTOML = ConfigBase; + +/// ConfigINI generates a "standard" INI compliant output +class ConfigINI : public ConfigTOML { + + public: + ConfigINI() { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; + valueDelimiter = '='; + } +}; + + + +class Option; /// @defgroup validator_group Validators + /// @brief Some validators that are provided /// -/// These are simple `void(std::string&)` validators that are useful. They throw -/// a ValidationError if they fail (or the normally expected error if the cast fails) +/// 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. /// @{ -/// Check for an existing file -inline std::string ExistingFile(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(); -} +/// +class Validator { + protected: + /// This is the description function, if empty the description_ will be used + std::function desc_function_{[]() { return std::string{}; }}; -/// Check for an existing directory -inline std::string ExistingDirectory(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(); -} + /// This is the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func_{[](std::string &) { return std::string{}; }}; + /// The name for search purposes of the Validator + std::string name_{}; + /// A Validator will only apply to an indexed value (-1 is all elements) + int application_index_ = -1; + /// Enable for Validator to allow it to be disabled if need be + bool active_{true}; + /// specify that a validator should not modify the input + bool non_modifying_{false}; + + Validator(std::string validator_desc, std::function func) + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(func)) {} + + public: + Validator() = default; + /// Construct a Validator with just the description string + explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {} + /// Construct Validator from basic information + Validator(std::function op, std::string validator_desc, std::string validator_name = "") + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)), + name_(std::move(validator_name)) {} + /// Set the Validator operation function + Validator &operation(std::function op) { + func_ = std::move(op); + return *this; + } + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(std::string &str) const; + + /// 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 { + std::string value = str; + return (active_) ? func_(value) : std::string{}; + } + + /// Specify the type string + Validator &description(std::string validator_desc) { + desc_function_ = [validator_desc]() { return validator_desc; }; + return *this; + } + /// Specify the type string + CLI11_NODISCARD Validator description(std::string validator_desc) const; + + /// Generate type description information for the Validator + CLI11_NODISCARD std::string get_description() const { + if(active_) { + return desc_function_(); + } + return std::string{}; + } + /// Specify the type string + Validator &name(std::string validator_name) { + name_ = std::move(validator_name); + return *this; + } + /// Specify the type string + CLI11_NODISCARD Validator name(std::string validator_name) const { + Validator newval(*this); + newval.name_ = std::move(validator_name); + return newval; + } + /// Get the name of the Validator + CLI11_NODISCARD const std::string &get_name() const { return name_; } + /// Specify whether the Validator is active or not + Validator &active(bool active_val = true) { + active_ = active_val; + return *this; + } + /// Specify whether the Validator is active or not + CLI11_NODISCARD Validator active(bool active_val = true) const { + Validator newval(*this); + newval.active_ = active_val; + return newval; + } + + /// Specify whether the Validator can be modifying or not + Validator &non_modifying(bool no_modify = true) { + non_modifying_ = no_modify; + return *this; + } + /// Specify the application index of a validator + Validator &application_index(int app_index) { + application_index_ = app_index; + return *this; + } + /// Specify the application index of a validator + CLI11_NODISCARD Validator application_index(int app_index) const { + Validator newval(*this); + newval.application_index_ = app_index; + return newval; + } + /// Get the current value of the application index + CLI11_NODISCARD int get_application_index() const { return application_index_; } + /// Get a boolean if the validator is active + CLI11_NODISCARD bool get_active() const { return active_; } + + /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input + CLI11_NODISCARD bool get_modifying() const { return !non_modifying_; } + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator&(const Validator &other) const; + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator|(const Validator &other) const; + + /// Create a validator that fails when a given validator succeeds + Validator operator!() const; + + private: + void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger); +}; + +/// Class wrapping some of the accessors of Validator +class CustomValidator : public Validator { + public: +}; +// 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 { + +/// CLI enumeration of different file types +enum class path_type { nonexistent, file, directory }; + +/// get the type of the path from a file name +CLI11_INLINE path_type check_path(const char *file) noexcept; + +/// Check for an existing file (returns error message if check fails) +class ExistingFileValidator : public Validator { + public: + ExistingFileValidator(); +}; + +/// Check for an existing directory (returns error message if check fails) +class ExistingDirectoryValidator : public Validator { + public: + ExistingDirectoryValidator(); +}; /// Check for an existing path -inline std::string ExistingPath(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(); +class ExistingPathValidator : public Validator { + public: + ExistingPathValidator(); +}; + +/// Check for an non-existing path +class NonexistentPathValidator : public Validator { + public: + NonexistentPathValidator(); +}; + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator(); +}; + +class EscapedStringTransformer : public Validator { + public: + EscapedStringTransformer(); +}; + +} // 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; + +/// Check for an IP4 address +const detail::IPV4Validator ValidIPV4; + +/// convert escaped characters into their associated values +const detail::EscapedStringTransformer EscapedString; + +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) + : Validator(validator_name, [](std::string &input_string) { + using CLI::detail::lexical_cast; + auto val = DesiredType(); + if(!lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string(); + }) {} + TypeValidator() : TypeValidator(detail::type_name()) {} +}; + +/// Check for a number +const TypeValidator Number("NUMBER"); + +/// Modify a path if the file is a particular default location, can be used as Check or transform +/// with the error return optionally disabled +class FileOnDefaultPath : public Validator { + public: + explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true); +}; + +/// Produce a range (factory). Min and max are inclusive. +class Range : public Validator { + public: + /// 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_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min_val << " - " << max_val << "]"; + description(out.str()); + } + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if((!converted) || (val < min_val || val > max_val)) { + std::stringstream out; + out << "Value " << input << " not in range ["; + out << min_val << " - " << max_val << "]"; + return out.str(); + } + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template + explicit Range(T max_val, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max_val, validator_name) {} +}; + +/// Check for a non negative number +const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), ::min here is the smallest positive number +const Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); + +/// Produce a bounded range (factory). Min and max are inclusive. +class Bound : public Validator { + public: + /// This bounds a value 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 Bound(T min_val, T max_val) { + std::stringstream out; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; + description(out.str()); + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); + + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} +}; + +namespace detail { +template ::type>::value, detail::enabler> = detail::dummy> +auto smart_deref(T value) -> decltype(*value) { + return *value; } -/// Check for a non-existing path -inline std::string NonexistentPath(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(); +template < + typename T, + enable_if_t::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type &smart_deref(T &value) { + return value; +} +/// Generate a string representation of a set +template std::string generate_set(const T &set) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); + out.push_back('}'); + return out; } -/// Produce a range validator function +/// Generate a string representation of a map +template std::string generate_map(const T &map, bool key_only = false) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); + out.push_back('}'); + return out; +} + +template struct has_find { + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; +}; + +/// A search function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + using element_t = typename detail::element_type::type; + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { + return (detail::pair_adaptor::first(v) == val); + }); + return {(it != std::end(setref)), it}; +} + +/// A search function that uses the built in find function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + auto &setref = detail::smart_deref(set); + auto it = setref.find(val); + return {(it != std::end(setref)), it}; +} + +/// A search function with a filter function +template +auto search(const T &set, const V &val, const std::function &filter_function) + -> std::pair { + using element_t = typename detail::element_type::type; + // do the potentially faster first search + auto res = search(set, val); + if((res.first) || (!(filter_function))) { + return res; + } + // if we haven't found it do the longer linear search with all the element translations + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); + }); + return {(it != std::end(setref)), it}; +} + +// the following suggestion was made by Nikita Ofitserov(@himikof) +// done in templates to prevent compiler warnings on negation of unsigned numbers + +/// Do a check for overflow on signed numbers template -std::function Range(T min, T max) { - return [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(); - }; +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + if((a > 0) == (b > 0)) { + return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); + } + return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); } - -/// Range of one value is 0 to value +/// Do a check for overflow on unsigned numbers template -std::function Range(T max) { - return Range(static_cast(0), max); +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + return ((std::numeric_limits::max)() / a < b); } +/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. +template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + if(a == 0 || b == 0 || a == 1 || b == 1) { + a *= b; + return true; + } + if(a == (std::numeric_limits::min)() || b == (std::numeric_limits::min)()) { + return false; + } + if(overflowCheck(a, b)) { + return false; + } + a *= b; + return true; +} + +/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. +template +typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + T c = a * b; + if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { + return false; + } + a = c; + return true; +} + +} // namespace detail +/// Verify items are in a set +class IsMember : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + IsMember(std::initializer_list values, Args &&...args) + : IsMember(std::vector(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit IsMember(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func_ = [set, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } + + // Return empty error string (success) + return std::string{}; + } + + // If you reach this point, the result was not found + return input + " not in " + detail::generate_set(detail::smart_deref(set)); + }; + } + + /// You can pass in as many filter functions as you like, they nest (string only currently) + template + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// definition of the default transformation object +template using TransformPairs = std::vector>; + +/// Translate named items to other or a value set +class Transformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + Transformer(std::initializer_list> values, Args &&...args) + : Transformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Transformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; + + func_ = [mapping, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// translate named items to other or a value set +class CheckedTransformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + CheckedTransformer(std::initializer_list> values, Args &&...args) + : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit CheckedTransformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + auto tfunc = [mapping]() { + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor::second(v)); }, + ","); + out.push_back('}'); + return out; + }; + + desc_function_ = tfunc; + + func_ = [mapping, tfunc, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + bool converted = lexical_cast(input, b); + if(converted) { + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + return std::string{}; + } + } + for(const auto &v : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); + if(output_string == input) { + return std::string(); + } + } + + return "Check " + input + " " + tfunc() + " FAILED"; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : CheckedTransformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// Helper function to allow ignore_case to be passed to IsMember or Transform +inline std::string ignore_case(std::string item) { return detail::to_lower(item); } + +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform +inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } + +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform +inline std::string ignore_space(std::string item) { + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); + return item; +} + +/// Multiply a number by a factor using given mapping. +/// Can be used to write transforms for SIZE or DURATION inputs. +/// +/// Example: +/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` +/// one can recognize inputs like "100", "12kb", "100 MB", +/// that will be automatically transformed to 100, 14448, 104857600. +/// +/// Output number type matches the type in the provided mapping. +/// Therefore, if it is required to interpret real inputs like "0.42 s", +/// the mapping should be of a type or . +class AsNumberWithUnit : public Validator { + public: + /// Adjust AsNumberWithUnit behavior. + /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. + /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError + /// if UNIT_REQUIRED is set and unit literal is not found. + enum Options { + CASE_SENSITIVE = 0, + CASE_INSENSITIVE = 1, + UNIT_OPTIONAL = 0, + UNIT_REQUIRED = 2, + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL + }; + + template + explicit AsNumberWithUnit(std::map mapping, + Options opts = DEFAULT, + const std::string &unit_name = "UNIT") { + description(generate_description(unit_name, opts)); + validate_mapping(mapping, opts); + + // transform function + func_ = [mapping, opts](std::string &input) -> std::string { + Number num{}; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + if(unit.empty()) { + using CLI::detail::lexical_cast; + if(!lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + if(!input.empty()) { + using CLI::detail::lexical_cast; + bool converted = lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); + } + + input = detail::to_string(num); + + return {}; + }; + } + + private: + /// Check that mapping contains valid units. + /// Update mapping for CASE_INSENSITIVE mode. + template static void validate_mapping(std::map &mapping, Options opts) { + for(auto &kv : mapping) { + if(kv.first.empty()) { + throw ValidationError("Unit must not be empty."); + } + if(!detail::isalpha(kv.first)) { + throw ValidationError("Unit must contain only letters."); + } + } + + // make all units lowercase if CASE_INSENSITIVE + if(opts & CASE_INSENSITIVE) { + std::map lower_mapping; + for(auto &kv : mapping) { + auto s = detail::to_lower(kv.first); + if(lower_mapping.count(s)) { + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); + } + lower_mapping[detail::to_lower(kv.first)] = kv.second; + } + mapping = std::move(lower_mapping); + } + } + + /// Generate description like this: NUMBER [UNIT] + template static std::string generate_description(const std::string &name, Options opts) { + std::stringstream out; + out << detail::type_name() << ' '; + if(opts & UNIT_REQUIRED) { + out << name; + } else { + out << '[' << name << ']'; + } + return out.str(); + } +}; + +inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +/// Converts a human-readable size string (with unit literal) to uin64_t size. +/// Example: +/// "100" => 100 +/// "1 b" => 100 +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) +/// "10 KB" => 10240 +/// "10 kb" => 10240 +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) +/// "10kb" => 10240 +/// "2 MB" => 2097152 +/// "2 EiB" => 2^61 // Units up to exibyte are supported +class AsSizeValue : public AsNumberWithUnit { + public: + using result_t = std::uint64_t; + + /// If kb_is_1000 is true, + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 + /// (same applies to higher order units as well). + /// Otherwise, interpret all literals as factors of 1024. + /// The first option is formally correct, but + /// the second interpretation is more wide-spread + /// (see https://en.wikipedia.org/wiki/Binary_prefix). + explicit AsSizeValue(bool kb_is_1000); + + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000); + + /// Cache calculated mapping + static std::map get_mapping(bool kb_is_1000); +}; + +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. +CLI11_INLINE std::pair split_program_name(std::string commandline); + +} // namespace detail /// @} -} // namespace CLI -// From CLI/Option.hpp -namespace CLI { + +CLI11_INLINE std::string Validator::operator()(std::string &str) const { + std::string retstring; + if(active_) { + if(non_modifying_) { + std::string value = str; + retstring = func_(value); + } else { + retstring = func_(str); + } + } + return retstring; +} + +CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; +} + +CLI11_INLINE Validator Validator::operator&(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " AND "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(!s1.empty() && !s2.empty()) + return std::string("(") + s1 + ") AND (" + s2 + ")"; + return s1 + s2; + }; + + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator|(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " OR "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(s1.empty() || s2.empty()) + return std::string(); + + return std::string("(") + s1 + ") OR (" + s2 + ")"; + }; + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator!() const { + Validator newval; + const std::function &dfunc1 = desc_function_; + newval.desc_function_ = [dfunc1]() { + auto str = dfunc1(); + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; + }; + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { + std::string s1 = f1(test); + if(s1.empty()) { + return std::string("check ") + dfunc1() + " succeeded improperly"; + } + return std::string{}; + }; + newval.active_ = active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE void +Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + + const std::function &dfunc1 = val1.desc_function_; + const std::function &dfunc2 = val2.desc_function_; + + desc_function_ = [=]() { + std::string f1 = dfunc1(); + std::string f2 = dfunc2(); + if((f1.empty()) || (f2.empty())) { + return f1 + f2; + } + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; + }; +} + +namespace detail { + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE path_type check_path(const char *file) noexcept { + std::error_code ec; + auto stat = std::filesystem::status(to_path(file), ec); + if(ec) { + return path_type::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: // LCOV_EXCL_LINE + case std::filesystem::file_type::not_found: + return path_type::nonexistent; // LCOV_EXCL_LINE + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } +} +#else +CLI11_INLINE path_type check_path(const char *file) noexcept { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistent; +} +#endif + +CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "File does not exist: " + filename; + } + if(path_result == path_type::directory) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Directory does not exist: " + filename; + } + if(path_result == path_type::file) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Path does not exist: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistent) { + return "Path already exists: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + auto result = CLI::detail::split(ip_addr, '.'); + if(result.size() != 4) { + return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; + } + int num = 0; + for(const auto &var : result) { + using CLI::detail::lexical_cast; + bool retval = lexical_cast(var, num); + if(!retval) { + return std::string("Failed parsing number (") + var + ')'; + } + if(num < 0 || num > 255) { + return std::string("Each IP number must be between 0 and 255 ") + var; + } + } + return std::string{}; + }; +} + +CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { + func_ = [](std::string &str) { + try { + if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && + str.front() == str.back()) { + process_quoted_string(str); + } else if(str.find_first_of('\\') != std::string::npos) { + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + } else { + str = remove_escaped_characters(str); + } + } + return std::string{}; + } catch(const std::invalid_argument &ia) { + return std::string(ia.what()); + } + }; +} +} // namespace detail + +CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) + : Validator("FILE") { + func_ = [default_path, enableErrorReturn](std::string &filename) { + auto path_result = detail::check_path(filename.c_str()); + if(path_result == detail::path_type::nonexistent) { + std::string test_file_path = default_path; + if(default_path.back() != '/' && default_path.back() != '\\') { + // Add folder separator + test_file_path += '/'; + } + test_file_path.append(filename); + path_result = detail::check_path(test_file_path.c_str()); + if(path_result == detail::path_type::file) { + filename = test_file_path; + } else { + if(enableErrorReturn) { + return "File does not exist: " + filename; + } + } + } + return std::string{}; + }; +} + +CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { + if(kb_is_1000) { + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); + } else { + description("SIZE [b, kb(=1024b), ...]"); + } +} + +CLI11_INLINE std::map AsSizeValue::init_mapping(bool kb_is_1000) { + std::map m; + result_t k_factor = kb_is_1000 ? 1000 : 1024; + result_t ki_factor = 1024; + result_t k = 1; + result_t ki = 1; + m["b"] = 1; + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { + k *= k_factor; + ki *= ki_factor; + m[p] = k; + m[p + "b"] = k; + m[p + "i"] = ki; + m[p + "ib"] = ki; + } + return m; +} + +CLI11_INLINE std::map AsSizeValue::get_mapping(bool kb_is_1000) { + if(kb_is_1000) { + static auto m = init_mapping(true); + return m; + } + static auto m = init_mapping(false); + return m; +} + +namespace detail { + +CLI11_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(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { + 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 + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + + break; + } + } + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + + // strip the program name + vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} + +} // namespace detail +/// @} + + + + +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 + std::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; + FormatterBase &operator=(const FormatterBase &) = default; + FormatterBase &operator=(FormatterBase &&) = default; + + /// Adding a destructor in this form to work around bug in GCC 4.7 + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-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(std::size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + CLI11_NODISCARD std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + return labels_.at(key); + } + + /// Get the current column width + CLI11_NODISCARD std::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)) {} + + /// Adding a destructor (mostly to make GCC 4.7 happy) + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) + + /// 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; + Formatter &operator=(const Formatter &) = default; + Formatter &operator=(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + CLI11_NODISCARD 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 * /*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; + + ///@} +}; + + + using results_t = std::vector; -using callback_t = std::function; +/// callback function definition +using callback_t = std::function; class Option; class App; using Option_p = std::unique_ptr