From a116f39e4949245989b79fb6d13cd19b38f0ae54 Mon Sep 17 00:00:00 2001 From: Whitney Armstrong <warmstrong@anl.gov> Date: Wed, 5 May 2021 15:51:16 -0500 Subject: [PATCH] Removed a lot of stuff that is now in common_bench See [common_bench](https://eicweb.phy.anl.gov/EIC/benchmarks/common_bench/) --- README.md | 7 + bin/gen_ci_config | 97 - include/benchmark.h | 124 - include/clipp.h | 6248 ------------------------------------ include/exception.h | 22 - include/mt.h | 11 - include/plot.h | 42 - include/util.h | 161 - options/.gitignore | 0 options/env.sh | 117 - tools/dev-shell | 84 - tools/download.sh | 73 - util/build_detector.sh | 72 - util/collect_benchmarks.py | 181 -- util/collect_tests.py | 204 -- util/compile_analyses.py | 119 - util/parse_cmd.sh | 127 - util/print_env.sh | 13 - util/run_many.py | 124 - 19 files changed, 7 insertions(+), 7819 deletions(-) delete mode 100755 bin/gen_ci_config delete mode 100644 include/benchmark.h delete mode 100644 include/clipp.h delete mode 100644 include/exception.h delete mode 100644 include/mt.h delete mode 100644 include/plot.h delete mode 100644 include/util.h create mode 100644 options/.gitignore delete mode 100755 options/env.sh delete mode 100755 tools/dev-shell delete mode 100755 tools/download.sh delete mode 100755 util/build_detector.sh delete mode 100755 util/collect_benchmarks.py delete mode 100755 util/collect_tests.py delete mode 100755 util/compile_analyses.py delete mode 100755 util/parse_cmd.sh delete mode 100755 util/print_env.sh delete mode 100755 util/run_many.py diff --git a/README.md b/README.md index 41ed579a..57c0823a 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,15 @@ Detector benchmarks are meant to test for regressions in individual detector sub The analysis is meant to avoid a reconstruction step. So this precludes using [juggler](https://eicweb.phy.anl.gov/EIC/juggler) for processing the events. +## Documentation + +See [common_bench](https://eicweb.phy.anl.gov/EIC/benchmarks/common_bench/). + ## Adding new benchmarks +See `benchmarks` directory for examples. + + ### Pass/Fail tests - Create a script that returns exit status 0 for success. diff --git a/bin/gen_ci_config b/bin/gen_ci_config deleted file mode 100755 index debe668f..00000000 --- a/bin/gen_ci_config +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash -set -o nounset -set -o errexit - -CI_TAG=sodium -BENCHMARK_SCRIPT_DIR=. -CI_JOB_PREFIX=cal_test_ - -function print_the_help { - echo "USAGE: $0 [-t <runner_tag>] " - echo " OPTIONS: " - echo " -i,--input Input scripts directory " - echo " -t,--tag Gitlab Runner tag" - echo " -p,--prefix job name prefix" - exit -} - -POSITIONAL=() -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - -h|--help) - shift # past argument - print_the_help - ;; - -i|--input) - BENCHMARK_SCRIPT_DIR="$2" - shift # past argument - shift # past value - ;; - -t|--tag) - CI_TAG="$2" - shift # past argument - shift # past value - ;; - -p|--prefix) - CI_JOB_PREFIX="$2" - shift # past argument - shift # past value - ;; - *) # unknown option - #POSITIONAL+=("$1") # save it in an array for later - echo "unknown option $1" - print_the_help - shift # past argument - ;; - esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -shopt -s nullglob -ifile=0 - - -cat <<EOF -stages: - #- simulate - - benchmarks -EOF - -for script_name in ${BENCHMARK_SCRIPT_DIR}/*.sh -do - filename=$(basename ${script_name}) - filename_noext="${filename%%.*}" - ifile=$((ifile+1)) - cat <<EOF -${CI_JOB_PREFIX}${ifile}_${filename_noext}: - stage: benchmarks - script: - - bash ${script_name} - artifact: - paths: - - results/ - allow_failure: true - -EOF -done - -for script_name in ${BENCHMARK_SCRIPT_DIR}/*.cxx -do - filename=$(basename ${script_name}) - filename_noext="${filename%%.*}" - ifile=$((ifile+1)) - cat <<EOF -${CI_JOB_PREFIX}${ifile}_${filename_noext}: - stage: benchmarks - script: - - root -b -q ${script_name} - artifact: - paths: - - results/ - allow_failure: true - -EOF -done - diff --git a/include/benchmark.h b/include/benchmark.h deleted file mode 100644 index 0634be0a..00000000 --- a/include/benchmark.h +++ /dev/null @@ -1,124 +0,0 @@ -#ifndef BENCHMARK_H -#define BENCHMARK_H - -#include "exception.h" -#include <fmt/core.h> -#include <fstream> -#include <iomanip> -#include <iostream> -#include <nlohmann/json.hpp> -#include <string> - -// Bookkeeping of test data to store data of one or more tests in a json file to -// facilitate future accounting. -// -// Usage Example 1 (single test): -// ============================== -// 1. define our test -// eic::util::Test test1{ -// {{"name", "example_test"}, -// {"title", "Example Test"}, -// {"description", "This is an example of a test definition"}, -// {"quantity", "efficiency"}, -// {"target", "1"}}}; -// 2. set pass/fail/error status and return value (in this case .99) -// test1.pass(0.99) -// 3. write our test data to a json file -// eic::util::write_test(test1, "test1.json"); -// -// Usage Example 2 (multiple tests): -// ================================= -// 1. define our tests -// eic::util::Test test1{ -// {{"name", "example_test"}, -// {"title", "Example Test"}, -// {"description", "This is an example of a test definition"}, -// {"quantity", "efficiency"}, -// {"target", "1"}}}; -// eic::util::Test test2{ -// {{"name", "another_test"}, -// {"title", "Another example Test"}, -// {"description", "This is a second example of a test definition"}, -// {"quantity", "resolution"}, -// {"target", "3."}}}; -// 2. set pass/fail/error status and return value (in this case .99) -// test1.fail(10) -// 3. write our test data to a json file -// eic::util::write_test({test1, test2}, "test.json"); - -// Namespace for utility scripts, FIXME this should be part of an independent -// library -namespace eic::util { - - struct TestDefinitionError : Exception { - TestDefinitionError(std::string_view msg) : Exception(msg, "test_definition_error") {} - }; - - // Wrapper for our test data json, with three methods to set the status - // after test completion (to pass, fail or error). The default value - // is error. - // The following fields should be defined in the definitions json - // for the test to make sense: - // - name: unique identifier for this test - // - title: Slightly more verbose identifier for this test - // - description: Concise description of what is tested - // - quantity: What quantity is tested? Unites of value/target - // - target: Target value of <quantity> that we want to reach - // - value: Actual value of <quantity> - // - weight: Weight for this test (this is defaulted to 1.0 if not specified) - // - result: pass/fail/error - struct Test { - // note: round braces for the json constructor, as else it will pick the wrong - // initializer-list constructur (it will put everything in an array) - Test(const std::map<std::string, std::string>& definition) : json(definition) - { - // std::cout << json.dump() << std::endl; - // initialize with error (as we don't have a value yet) - error(); - // Check that all required fields are present - for (const auto& field : - {"name", "title", "description", "quantity", "target", "value", "result"}) { - if (json.find(field) == json.end()) { - throw TestDefinitionError{ - fmt::format("Error in test definition: field '{}' missing", field)}; - } - } - // Default "weight" to 1 if not set - if (json.find("weight") == json.end()) { - json["weight"] = 1.0; - } - } - // Set this test to pass/fail/error - void pass(double value) { update_result("pass", value); } - void fail(double value) { update_result("fail", value); } - void error(double value = 0) { update_result("error", value); } - - nlohmann::json json; - - private: - void update_result(std::string_view status, double value) - { - json["result"] = status; - json["value"] = value; - } - }; - - void write_test(const std::vector<Test>& data, const std::string& fname) - { - nlohmann::json test; - for (auto& entry : data) { - test["tests"].push_back(entry.json); - } - std::cout << fmt::format("Writing test data to {}\n", fname); - std::ofstream output_file(fname); - output_file << std::setw(4) << test << "\n"; - } - void write_test(const Test& data, const std::string& fname) - { - std::vector<Test> vtd{data}; - write_test(vtd, fname); - } - -} // namespace eic::util - -#endif diff --git a/include/clipp.h b/include/clipp.h deleted file mode 100644 index a2a0cf8b..00000000 --- a/include/clipp.h +++ /dev/null @@ -1,6248 +0,0 @@ -/***************************************************************************** - * - * CLIPP - command line interfaces for modern C++ - * - * released under MIT license - * - * (c) 2017 André Müller; foss@andremueller-online.de - * - *****************************************************************************/ - -#ifndef AM_CLIPP_H__ -#define AM_CLIPP_H__ - -#include <cstring> -#include <string> -#include <cstdlib> -#include <cstring> -#include <cctype> -#include <cmath> -#include <memory> -#include <vector> -#include <limits> -#include <stack> -#include <algorithm> -#include <sstream> -#include <utility> -#include <iterator> -#include <functional> - - -/*************************************************************************//** - * - * @brief primary namespace - * - *****************************************************************************/ -namespace clipp { - - - -/***************************************************************************** - * - * basic constants and datatype definitions - * - *****************************************************************************/ -using arg_index = int; - -using arg_string = std::string; -using doc_string = std::string; - -using arg_list = std::vector<arg_string>; - - - -/*************************************************************************//** - * - * @brief tristate - * - *****************************************************************************/ -enum class tri : char { no, yes, either }; - -inline constexpr bool operator == (tri t, bool b) noexcept { - return b ? t != tri::no : t != tri::yes; -} -inline constexpr bool operator == (bool b, tri t) noexcept { return (t == b); } -inline constexpr bool operator != (tri t, bool b) noexcept { return !(t == b); } -inline constexpr bool operator != (bool b, tri t) noexcept { return !(t == b); } - - - -/*************************************************************************//** - * - * @brief (start,size) index range - * - *****************************************************************************/ -class subrange { -public: - using size_type = arg_string::size_type; - - /** @brief default: no match */ - explicit constexpr - subrange() noexcept : - at_{arg_string::npos}, length_{0} - {} - - /** @brief match length & position within subject string */ - explicit constexpr - subrange(size_type pos, size_type len) noexcept : - at_{pos}, length_{len} - {} - - /** @brief position of the match within the subject string */ - constexpr size_type at() const noexcept { return at_; } - /** @brief length of the matching subsequence */ - constexpr size_type length() const noexcept { return length_; } - - /** @brief returns true, if query string is a prefix of the subject string */ - constexpr bool prefix() const noexcept { - return at_ == 0 && length_ > 0; - } - - /** @brief returns true, if query is a substring of the query string */ - constexpr explicit operator bool () const noexcept { - return at_ != arg_string::npos && length_ > 0; - } - -private: - size_type at_; - size_type length_; -}; - - - -/*************************************************************************//** - * - * @brief match predicates - * - *****************************************************************************/ -using match_predicate = std::function<bool(const arg_string&)>; -using match_function = std::function<subrange(const arg_string&)>; - - - - - - -/*************************************************************************//** - * - * @brief type traits (NOT FOR DIRECT USE IN CLIENT CODE!) - * no interface guarantees; might be changed or removed in the future - * - *****************************************************************************/ -namespace traits { - -/*************************************************************************//** - * - * @brief function (class) signature type trait - * - *****************************************************************************/ -template<class Fn, class Ret, class... Args> -constexpr auto -check_is_callable(int) -> decltype( - std::declval<Fn>()(std::declval<Args>()...), - std::integral_constant<bool, - std::is_same<Ret,typename std::result_of<Fn(Args...)>::type>::value>{} ); - -template<class,class,class...> -constexpr auto -check_is_callable(long) -> std::false_type; - -template<class Fn, class Ret> -constexpr auto -check_is_callable_without_arg(int) -> decltype( - std::declval<Fn>()(), - std::integral_constant<bool, - std::is_same<Ret,typename std::result_of<Fn()>::type>::value>{} ); - -template<class,class> -constexpr auto -check_is_callable_without_arg(long) -> std::false_type; - - - -template<class Fn, class... Args> -constexpr auto -check_is_void_callable(int) -> decltype( - std::declval<Fn>()(std::declval<Args>()...), std::true_type{}); - -template<class,class,class...> -constexpr auto -check_is_void_callable(long) -> std::false_type; - -template<class Fn> -constexpr auto -check_is_void_callable_without_arg(int) -> decltype( - std::declval<Fn>()(), std::true_type{}); - -template<class> -constexpr auto -check_is_void_callable_without_arg(long) -> std::false_type; - - - -template<class Fn, class Ret> -struct is_callable; - - -template<class Fn, class Ret, class... Args> -struct is_callable<Fn, Ret(Args...)> : - decltype(check_is_callable<Fn,Ret,Args...>(0)) -{}; - -template<class Fn, class Ret> -struct is_callable<Fn,Ret()> : - decltype(check_is_callable_without_arg<Fn,Ret>(0)) -{}; - - -template<class Fn, class... Args> -struct is_callable<Fn, void(Args...)> : - decltype(check_is_void_callable<Fn,Args...>(0)) -{}; - -template<class Fn> -struct is_callable<Fn,void()> : - decltype(check_is_void_callable_without_arg<Fn>(0)) -{}; - - - -/*************************************************************************//** - * - * @brief input range type trait - * - *****************************************************************************/ -template<class T> -constexpr auto -check_is_input_range(int) -> decltype( - begin(std::declval<T>()), end(std::declval<T>()), - std::true_type{}); - -template<class T> -constexpr auto -check_is_input_range(char) -> decltype( - std::begin(std::declval<T>()), std::end(std::declval<T>()), - std::true_type{}); - -template<class> -constexpr auto -check_is_input_range(long) -> std::false_type; - -template<class T> -struct is_input_range : - decltype(check_is_input_range<T>(0)) -{}; - - - -/*************************************************************************//** - * - * @brief size() member type trait - * - *****************************************************************************/ -template<class T> -constexpr auto -check_has_size_getter(int) -> - decltype(std::declval<T>().size(), std::true_type{}); - -template<class> -constexpr auto -check_has_size_getter(long) -> std::false_type; - -template<class T> -struct has_size_getter : - decltype(check_has_size_getter<T>(0)) -{}; - -} // namespace traits - - - - - - -/*************************************************************************//** - * - * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!) - * no interface guarantees; might be changed or removed in the future - * - *****************************************************************************/ -namespace detail { - - -/*************************************************************************//** - * @brief forwards string to first non-whitespace char; - * std string -> unsigned conv yields max value, but we want 0; - * also checks for nullptr - *****************************************************************************/ -inline bool -fwd_to_unsigned_int(const char*& s) -{ - if(!s) return false; - for(; std::isspace(*s); ++s); - if(!s[0] || s[0] == '-') return false; - if(s[0] == '-') return false; - return true; -} - - -/*************************************************************************//** - * - * @brief value limits clamping - * - *****************************************************************************/ -template<class T, class V, bool = (sizeof(V) > sizeof(T))> -struct limits_clamped { - static T from(const V& v) { - if(v > V(std::numeric_limits<T>::max())) { - return std::numeric_limits<T>::max(); - } - if(v < V(std::numeric_limits<T>::lowest())) { - return std::numeric_limits<T>::lowest(); - } - return T(v); - } -}; - -template<class T, class V> -struct limits_clamped<T,V,false> { - static T from(const V& v) { return T(v); } -}; - - -/*************************************************************************//** - * - * @brief returns value of v as a T, clamped at T's maximum - * - *****************************************************************************/ -template<class T, class V> -inline T clamped_on_limits(const V& v) { - return limits_clamped<T,V>::from(v); -} - - - - -/*************************************************************************//** - * - * @brief type conversion helpers - * - *****************************************************************************/ -template<class T> -struct make; - -template<> -struct make<bool> { - static inline bool from(const char* s) { - if(!s) return false; - return static_cast<bool>(s); - } -}; - -template<> -struct make<unsigned char> { - static inline unsigned char from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits<unsigned char>(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make<unsigned short int> { - static inline unsigned short int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits<unsigned short int>(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make<unsigned int> { - static inline unsigned int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits<unsigned int>(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make<unsigned long int> { - static inline unsigned long int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits<unsigned long int>(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make<unsigned long long int> { - static inline unsigned long long int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits<unsigned long long int>(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make<char> { - static inline char from(const char* s) { - //parse as single character? - const auto n = std::strlen(s); - if(n == 1) return s[0]; - //parse as integer - return clamped_on_limits<char>(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make<short int> { - static inline short int from(const char* s) { - return clamped_on_limits<short int>(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make<int> { - static inline int from(const char* s) { - return clamped_on_limits<int>(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make<long int> { - static inline long int from(const char* s) { - return clamped_on_limits<long int>(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make<long long int> { - static inline long long int from(const char* s) { - return (std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make<float> { - static inline float from(const char* s) { - return (std::strtof(s,nullptr)); - } -}; - -template<> -struct make<double> { - static inline double from(const char* s) { - return (std::strtod(s,nullptr)); - } -}; - -template<> -struct make<long double> { - static inline long double from(const char* s) { - return (std::strtold(s,nullptr)); - } -}; - -template<> -struct make<std::string> { - static inline std::string from(const char* s) { - return std::string(s); - } -}; - - - -/*************************************************************************//** - * - * @brief assigns boolean constant to one or multiple target objects - * - *****************************************************************************/ -template<class T, class V = T> -class assign_value -{ -public: - template<class X> - explicit constexpr - assign_value(T& target, X&& value) noexcept : - t_{std::addressof(target)}, v_{std::forward<X>(value)} - {} - - void operator () () const { - if(t_) *t_ = v_; - } - -private: - T* t_; - V v_; -}; - - - -/*************************************************************************//** - * - * @brief flips bools - * - *****************************************************************************/ -class flip_bool -{ -public: - explicit constexpr - flip_bool(bool& target) noexcept : - b_{&target} - {} - - void operator () () const { - if(b_) *b_ = !*b_; - } - -private: - bool* b_; -}; - - - -/*************************************************************************//** - * - * @brief increments using operator ++ - * - *****************************************************************************/ -template<class T> -class increment -{ -public: - explicit constexpr - increment(T& target) noexcept : t_{std::addressof(target)} {} - - void operator () () const { - if(t_) ++(*t_); - } - -private: - T* t_; -}; - - - -/*************************************************************************//** - * - * @brief decrements using operator -- - * - *****************************************************************************/ -template<class T> -class decrement -{ -public: - explicit constexpr - decrement(T& target) noexcept : t_{std::addressof(target)} {} - - void operator () () const { - if(t_) --(*t_); - } - -private: - T* t_; -}; - - - -/*************************************************************************//** - * - * @brief increments by a fixed amount using operator += - * - *****************************************************************************/ -template<class T> -class increment_by -{ -public: - explicit constexpr - increment_by(T& target, T by) noexcept : - t_{std::addressof(target)}, by_{std::move(by)} - {} - - void operator () () const { - if(t_) (*t_) += by_; - } - -private: - T* t_; - T by_; -}; - - - - -/*************************************************************************//** - * - * @brief makes a value from a string and assigns it to an object - * - *****************************************************************************/ -template<class T> -class map_arg_to -{ -public: - explicit constexpr - map_arg_to(T& target) noexcept : t_{std::addressof(target)} {} - - void operator () (const char* s) const { - if(t_ && s && (std::strlen(s) > 0)) - *t_ = detail::make<T>::from(s); - } - -private: - T* t_; -}; - - -//------------------------------------------------------------------- -/** - * @brief specialization for vectors: append element - */ -template<class T> -class map_arg_to<std::vector<T>> -{ -public: - map_arg_to(std::vector<T>& target): t_{std::addressof(target)} {} - - void operator () (const char* s) const { - if(t_ && s) t_->push_back(detail::make<T>::from(s)); - } - -private: - std::vector<T>* t_; -}; - - -//------------------------------------------------------------------- -/** - * @brief specialization for bools: - * set to true regardless of string content - */ -template<> -class map_arg_to<bool> -{ -public: - map_arg_to(bool& target): t_{&target} {} - - void operator () (const char* s) const { - if(t_ && s) *t_ = true; - } - -private: - bool* t_; -}; - - -} // namespace detail - - - - - - -/*************************************************************************//** - * - * @brief string matching and processing tools - * - *****************************************************************************/ - -namespace str { - - -/*************************************************************************//** - * - * @brief converts string to value of target type 'T' - * - *****************************************************************************/ -template<class T> -T make(const arg_string& s) -{ - return detail::make<T>::from(s); -} - - - -/*************************************************************************//** - * - * @brief removes trailing whitespace from string - * - *****************************************************************************/ -template<class C, class T, class A> -inline void -trimr(std::basic_string<C,T,A>& s) -{ - if(s.empty()) return; - - s.erase( - std::find_if_not(s.rbegin(), s.rend(), - [](char c) { return std::isspace(c);} ).base(), - s.end() ); -} - - -/*************************************************************************//** - * - * @brief removes leading whitespace from string - * - *****************************************************************************/ -template<class C, class T, class A> -inline void -triml(std::basic_string<C,T,A>& s) -{ - if(s.empty()) return; - - s.erase( - s.begin(), - std::find_if_not(s.begin(), s.end(), - [](char c) { return std::isspace(c);}) - ); -} - - -/*************************************************************************//** - * - * @brief removes leading and trailing whitespace from string - * - *****************************************************************************/ -template<class C, class T, class A> -inline void -trim(std::basic_string<C,T,A>& s) -{ - triml(s); - trimr(s); -} - - -/*************************************************************************//** - * - * @brief removes all whitespaces from string - * - *****************************************************************************/ -template<class C, class T, class A> -inline void -remove_ws(std::basic_string<C,T,A>& s) -{ - if(s.empty()) return; - - s.erase(std::remove_if(s.begin(), s.end(), - [](char c) { return std::isspace(c); }), - s.end() ); -} - - -/*************************************************************************//** - * - * @brief returns true, if the 'prefix' argument - * is a prefix of the 'subject' argument - * - *****************************************************************************/ -template<class C, class T, class A> -inline bool -has_prefix(const std::basic_string<C,T,A>& subject, - const std::basic_string<C,T,A>& prefix) -{ - if(prefix.size() > subject.size()) return false; - return subject.find(prefix) == 0; -} - - -/*************************************************************************//** - * - * @brief returns true, if the 'postfix' argument - * is a postfix of the 'subject' argument - * - *****************************************************************************/ -template<class C, class T, class A> -inline bool -has_postfix(const std::basic_string<C,T,A>& subject, - const std::basic_string<C,T,A>& postfix) -{ - if(postfix.size() > subject.size()) return false; - return (subject.size() - postfix.size()) == subject.find(postfix); -} - - - -/*************************************************************************//** -* -* @brief returns longest common prefix of several -* sequential random access containers -* -* @details InputRange require begin and end (member functions or overloads) -* the elements of InputRange require a size() member -* -*****************************************************************************/ -template<class InputRange> -auto -longest_common_prefix(const InputRange& strs) - -> typename std::decay<decltype(*begin(strs))>::type -{ - static_assert(traits::is_input_range<InputRange>(), - "parameter must satisfy the InputRange concept"); - - static_assert(traits::has_size_getter< - typename std::decay<decltype(*begin(strs))>::type>(), - "elements of input range must have a ::size() member function"); - - using std::begin; - using std::end; - - using item_t = typename std::decay<decltype(*begin(strs))>::type; - using str_size_t = typename std::decay<decltype(begin(strs)->size())>::type; - - const auto n = size_t(distance(begin(strs), end(strs))); - if(n < 1) return item_t(""); - if(n == 1) return *begin(strs); - - //length of shortest string - auto m = std::min_element(begin(strs), end(strs), - [](const item_t& a, const item_t& b) { - return a.size() < b.size(); })->size(); - - //check each character until we find a mismatch - for(str_size_t i = 0; i < m; ++i) { - for(str_size_t j = 1; j < n; ++j) { - if(strs[j][i] != strs[j-1][i]) - return strs[0].substr(0, i); - } - } - return strs[0].substr(0, m); -} - - - -/*************************************************************************//** - * - * @brief returns longest substring range that could be found in 'arg' - * - * @param arg string to be searched in - * @param substrings range of candidate substrings - * - *****************************************************************************/ -template<class C, class T, class A, class InputRange> -subrange -longest_substring_match(const std::basic_string<C,T,A>& arg, - const InputRange& substrings) -{ - using string_t = std::basic_string<C,T,A>; - - static_assert(traits::is_input_range<InputRange>(), - "parameter must satisfy the InputRange concept"); - - static_assert(std::is_same<string_t, - typename std::decay<decltype(*begin(substrings))>::type>(), - "substrings must have same type as 'arg'"); - - auto i = string_t::npos; - auto n = string_t::size_type(0); - for(const auto& s : substrings) { - auto j = arg.find(s); - if(j != string_t::npos && s.size() > n) { - i = j; - n = s.size(); - } - } - return subrange{i,n}; -} - - - -/*************************************************************************//** - * - * @brief returns longest prefix range that could be found in 'arg' - * - * @param arg string to be searched in - * @param prefixes range of candidate prefix strings - * - *****************************************************************************/ -template<class C, class T, class A, class InputRange> -subrange -longest_prefix_match(const std::basic_string<C,T,A>& arg, - const InputRange& prefixes) -{ - using string_t = std::basic_string<C,T,A>; - using s_size_t = typename string_t::size_type; - - static_assert(traits::is_input_range<InputRange>(), - "parameter must satisfy the InputRange concept"); - - static_assert(std::is_same<string_t, - typename std::decay<decltype(*begin(prefixes))>::type>(), - "prefixes must have same type as 'arg'"); - - auto i = string_t::npos; - auto n = s_size_t(0); - for(const auto& s : prefixes) { - auto j = arg.find(s); - if(j == 0 && s.size() > n) { - i = 0; - n = s.size(); - } - } - return subrange{i,n}; -} - - - -/*************************************************************************//** - * - * @brief returns the first occurrence of 'query' within 'subject' - * - *****************************************************************************/ -template<class C, class T, class A> -inline subrange -substring_match(const std::basic_string<C,T,A>& subject, - const std::basic_string<C,T,A>& query) -{ - if(subject.empty() || query.empty()) return subrange{}; - auto i = subject.find(query); - if(i == std::basic_string<C,T,A>::npos) return subrange{}; - return subrange{i,query.size()}; -} - - - -/*************************************************************************//** - * - * @brief returns first substring match (pos,len) within the input string - * that represents a number - * (with at maximum one decimal point and digit separators) - * - *****************************************************************************/ -template<class C, class T, class A> -subrange -first_number_match(std::basic_string<C,T,A> s, - C digitSeparator = C(','), - C decimalPoint = C('.'), - C exponential = C('e')) -{ - using string_t = std::basic_string<C,T,A>; - - str::trim(s); - if(s.empty()) return subrange{}; - - auto i = s.find_first_of("0123456789+-"); - if(i == string_t::npos) { - i = s.find(decimalPoint); - if(i == string_t::npos) return subrange{}; - } - - bool point = false; - bool sep = false; - auto exp = string_t::npos; - auto j = i + 1; - for(; j < s.size(); ++j) { - if(s[j] == digitSeparator) { - if(!sep) sep = true; else break; - } - else { - sep = false; - if(s[j] == decimalPoint) { - //only one decimal point before exponent allowed - if(!point && exp == string_t::npos) point = true; else break; - } - else if(std::tolower(s[j]) == std::tolower(exponential)) { - //only one exponent separator allowed - if(exp == string_t::npos) exp = j; else break; - } - else if(exp != string_t::npos && (exp+1) == j) { - //only sign or digit after exponent separator - if(s[j] != '+' && s[j] != '-' && !std::isdigit(s[j])) break; - } - else if(!std::isdigit(s[j])) { - break; - } - } - } - - //if length == 1 then must be a digit - if(j-i == 1 && !std::isdigit(s[i])) return subrange{}; - - return subrange{i,j-i}; -} - - - -/*************************************************************************//** - * - * @brief returns first substring match (pos,len) - * that represents an integer (with optional digit separators) - * - *****************************************************************************/ -template<class C, class T, class A> -subrange -first_integer_match(std::basic_string<C,T,A> s, - C digitSeparator = C(',')) -{ - using string_t = std::basic_string<C,T,A>; - - str::trim(s); - if(s.empty()) return subrange{}; - - auto i = s.find_first_of("0123456789+-"); - if(i == string_t::npos) return subrange{}; - - bool sep = false; - auto j = i + 1; - for(; j < s.size(); ++j) { - if(s[j] == digitSeparator) { - if(!sep) sep = true; else break; - } - else { - sep = false; - if(!std::isdigit(s[j])) break; - } - } - - //if length == 1 then must be a digit - if(j-i == 1 && !std::isdigit(s[i])) return subrange{}; - - return subrange{i,j-i}; -} - - - -/*************************************************************************//** - * - * @brief returns true if candidate string represents a number - * - *****************************************************************************/ -template<class C, class T, class A> -bool represents_number(const std::basic_string<C,T,A>& candidate, - C digitSeparator = C(','), - C decimalPoint = C('.'), - C exponential = C('e')) -{ - const auto match = str::first_number_match(candidate, digitSeparator, - decimalPoint, exponential); - - return (match && match.length() == candidate.size()); -} - - - -/*************************************************************************//** - * - * @brief returns true if candidate string represents an integer - * - *****************************************************************************/ -template<class C, class T, class A> -bool represents_integer(const std::basic_string<C,T,A>& candidate, - C digitSeparator = C(',')) -{ - const auto match = str::first_integer_match(candidate, digitSeparator); - return (match && match.length() == candidate.size()); -} - -} // namespace str - - - - - - -/*************************************************************************//** - * - * @brief makes function object with a const char* parameter - * that assigns a value to a ref-captured object - * - *****************************************************************************/ -template<class T, class V> -inline detail::assign_value<T,V> -set(T& target, V value) { - return detail::assign_value<T>{target, std::move(value)}; -} - - - -/*************************************************************************//** - * - * @brief makes parameter-less function object - * that assigns value(s) to a ref-captured object; - * value(s) are obtained by converting the const char* argument to - * the captured object types; - * bools are always set to true if the argument is not nullptr - * - *****************************************************************************/ -template<class T> -inline detail::map_arg_to<T> -set(T& target) { - return detail::map_arg_to<T>{target}; -} - - - -/*************************************************************************//** - * - * @brief makes function object that sets a bool to true - * - *****************************************************************************/ -inline detail::assign_value<bool> -set(bool& target) { - return detail::assign_value<bool>{target,true}; -} - -/*************************************************************************//** - * - * @brief makes function object that sets a bool to false - * - *****************************************************************************/ -inline detail::assign_value<bool> -unset(bool& target) { - return detail::assign_value<bool>{target,false}; -} - -/*************************************************************************//** - * - * @brief makes function object that flips the value of a ref-captured bool - * - *****************************************************************************/ -inline detail::flip_bool -flip(bool& b) { - return detail::flip_bool(b); -} - - - - - -/*************************************************************************//** - * - * @brief makes function object that increments using operator ++ - * - *****************************************************************************/ -template<class T> -inline detail::increment<T> -increment(T& target) { - return detail::increment<T>{target}; -} - -/*************************************************************************//** - * - * @brief makes function object that decrements using operator -- - * - *****************************************************************************/ -template<class T> -inline detail::increment_by<T> -increment(T& target, T by) { - return detail::increment_by<T>{target, std::move(by)}; -} - -/*************************************************************************//** - * - * @brief makes function object that increments by a fixed amount using operator += - * - *****************************************************************************/ -template<class T> -inline detail::decrement<T> -decrement(T& target) { - return detail::decrement<T>{target}; -} - - - - - - -/*************************************************************************//** - * - * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!) - * - *****************************************************************************/ -namespace detail { - - -/*************************************************************************//** - * - * @brief mixin that provides action definition and execution - * - *****************************************************************************/ -template<class Derived> -class action_provider -{ -private: - //--------------------------------------------------------------- - using simple_action = std::function<void()>; - using arg_action = std::function<void(const char*)>; - using index_action = std::function<void(int)>; - - //----------------------------------------------------- - class simple_action_adapter { - public: - simple_action_adapter() = default; - simple_action_adapter(const simple_action& a): action_(a) {} - simple_action_adapter(simple_action&& a): action_(std::move(a)) {} - void operator() (const char*) const { action_(); } - void operator() (int) const { action_(); } - private: - simple_action action_; - }; - - -public: - //--------------------------------------------------------------- - /** @brief adds an action that has an operator() that is callable - * with a 'const char*' argument */ - Derived& - call(arg_action a) { - argActions_.push_back(std::move(a)); - return *static_cast<Derived*>(this); - } - - /** @brief adds an action that has an operator()() */ - Derived& - call(simple_action a) { - argActions_.push_back(simple_action_adapter(std::move(a))); - return *static_cast<Derived*>(this); - } - - /** @brief adds an action that has an operator() that is callable - * with a 'const char*' argument */ - Derived& operator () (arg_action a) { return call(std::move(a)); } - - /** @brief adds an action that has an operator()() */ - Derived& operator () (simple_action a) { return call(std::move(a)); } - - - //--------------------------------------------------------------- - /** @brief adds an action that will set the value of 't' from - * a 'const char*' arg */ - template<class Target> - Derived& - set(Target& t) { - return call(clipp::set(t)); - } - - /** @brief adds an action that will set the value of 't' to 'v' */ - template<class Target, class Value> - Derived& - set(Target& t, Value&& v) { - return call(clipp::set(t, std::forward<Value>(v))); - } - - - //--------------------------------------------------------------- - /** @brief adds an action that will be called if a parameter - * matches an argument for the 2nd, 3rd, 4th, ... time - */ - Derived& - if_repeated(simple_action a) { - repeatActions_.push_back(simple_action_adapter{std::move(a)}); - return *static_cast<Derived*>(this); - } - /** @brief adds an action that will be called with the argument's - * index if a parameter matches an argument for - * the 2nd, 3rd, 4th, ... time - */ - Derived& - if_repeated(index_action a) { - repeatActions_.push_back(std::move(a)); - return *static_cast<Derived*>(this); - } - - - //--------------------------------------------------------------- - /** @brief adds an action that will be called if a required parameter - * is missing - */ - Derived& - if_missing(simple_action a) { - missingActions_.push_back(simple_action_adapter{std::move(a)}); - return *static_cast<Derived*>(this); - } - /** @brief adds an action that will be called if a required parameter - * is missing; the action will get called with the index of - * the command line argument where the missing event occured first - */ - Derived& - if_missing(index_action a) { - missingActions_.push_back(std::move(a)); - return *static_cast<Derived*>(this); - } - - - //--------------------------------------------------------------- - /** @brief adds an action that will be called if a parameter - * was matched, but was unreachable in the current scope - */ - Derived& - if_blocked(simple_action a) { - blockedActions_.push_back(simple_action_adapter{std::move(a)}); - return *static_cast<Derived*>(this); - } - /** @brief adds an action that will be called if a parameter - * was matched, but was unreachable in the current scope; - * the action will be called with the index of - * the command line argument where the problem occured - */ - Derived& - if_blocked(index_action a) { - blockedActions_.push_back(std::move(a)); - return *static_cast<Derived*>(this); - } - - - //--------------------------------------------------------------- - /** @brief adds an action that will be called if a parameter match - * was in conflict with a different alternative parameter - */ - Derived& - if_conflicted(simple_action a) { - conflictActions_.push_back(simple_action_adapter{std::move(a)}); - return *static_cast<Derived*>(this); - } - /** @brief adds an action that will be called if a parameter match - * was in conflict with a different alternative paramete; - * the action will be called with the index of - * the command line argument where the problem occuredr - */ - Derived& - if_conflicted(index_action a) { - conflictActions_.push_back(std::move(a)); - return *static_cast<Derived*>(this); - } - - - //--------------------------------------------------------------- - /** @brief adds targets = either objects whose values should be - * set by command line arguments or actions that should - * be called in case of a match */ - template<class T, class... Ts> - Derived& - target(T&& t, Ts&&... ts) { - target(std::forward<T>(t)); - target(std::forward<Ts>(ts)...); - return *static_cast<Derived*>(this); - } - - /** @brief adds action that should be called in case of a match */ - template<class T, class = typename std::enable_if< - !std::is_fundamental<typename std::decay<T>::type>() && - (traits::is_callable<T,void()>() || - traits::is_callable<T,void(const char*)>() ) - >::type> - Derived& - target(T&& t) { - call(std::forward<T>(t)); - return *static_cast<Derived*>(this); - } - - /** @brief adds object whose value should be set by command line arguments - */ - template<class T, class = typename std::enable_if< - std::is_fundamental<typename std::decay<T>::type>() || - (!traits::is_callable<T,void()>() && - !traits::is_callable<T,void(const char*)>() ) - >::type> - Derived& - target(T& t) { - set(t); - return *static_cast<Derived*>(this); - } - - //TODO remove ugly empty param list overload - Derived& - target() { - return *static_cast<Derived*>(this); - } - - - //--------------------------------------------------------------- - /** @brief adds target, see member function 'target' */ - template<class Target> - inline friend Derived& - operator << (Target&& t, Derived& p) { - p.target(std::forward<Target>(t)); - return p; - } - /** @brief adds target, see member function 'target' */ - template<class Target> - inline friend Derived&& - operator << (Target&& t, Derived&& p) { - p.target(std::forward<Target>(t)); - return std::move(p); - } - - //----------------------------------------------------- - /** @brief adds target, see member function 'target' */ - template<class Target> - inline friend Derived& - operator >> (Derived& p, Target&& t) { - p.target(std::forward<Target>(t)); - return p; - } - /** @brief adds target, see member function 'target' */ - template<class Target> - inline friend Derived&& - operator >> (Derived&& p, Target&& t) { - p.target(std::forward<Target>(t)); - return std::move(p); - } - - - //--------------------------------------------------------------- - /** @brief executes all argument actions */ - void execute_actions(const arg_string& arg) const { - int i = 0; - for(const auto& a : argActions_) { - ++i; - a(arg.c_str()); - } - } - - /** @brief executes repeat actions */ - void notify_repeated(arg_index idx) const { - for(const auto& a : repeatActions_) a(idx); - } - /** @brief executes missing error actions */ - void notify_missing(arg_index idx) const { - for(const auto& a : missingActions_) a(idx); - } - /** @brief executes blocked error actions */ - void notify_blocked(arg_index idx) const { - for(const auto& a : blockedActions_) a(idx); - } - /** @brief executes conflict error actions */ - void notify_conflict(arg_index idx) const { - for(const auto& a : conflictActions_) a(idx); - } - -private: - //--------------------------------------------------------------- - std::vector<arg_action> argActions_; - std::vector<index_action> repeatActions_; - std::vector<index_action> missingActions_; - std::vector<index_action> blockedActions_; - std::vector<index_action> conflictActions_; -}; - - - - - - -/*************************************************************************//** - * - * @brief mixin that provides basic common settings of parameters and groups - * - *****************************************************************************/ -template<class Derived> -class token -{ -public: - //--------------------------------------------------------------- - using doc_string = clipp::doc_string; - - - //--------------------------------------------------------------- - /** @brief returns documentation string */ - const doc_string& doc() const noexcept { - return doc_; - } - - /** @brief sets documentations string */ - Derived& doc(const doc_string& txt) { - doc_ = txt; - return *static_cast<Derived*>(this); - } - - /** @brief sets documentations string */ - Derived& doc(doc_string&& txt) { - doc_ = std::move(txt); - return *static_cast<Derived*>(this); - } - - - //--------------------------------------------------------------- - /** @brief returns if a group/parameter is repeatable */ - bool repeatable() const noexcept { - return repeatable_; - } - - /** @brief sets repeatability of group/parameter */ - Derived& repeatable(bool yes) noexcept { - repeatable_ = yes; - return *static_cast<Derived*>(this); - } - - - //--------------------------------------------------------------- - /** @brief returns if a group/parameter is blocking/positional */ - bool blocking() const noexcept { - return blocking_; - } - - /** @brief determines, if a group/parameter is blocking/positional */ - Derived& blocking(bool yes) noexcept { - blocking_ = yes; - return *static_cast<Derived*>(this); - } - - -private: - //--------------------------------------------------------------- - doc_string doc_; - bool repeatable_ = false; - bool blocking_ = false; -}; - - - - -/*************************************************************************//** - * - * @brief sets documentation strings on a token - * - *****************************************************************************/ -template<class T> -inline T& -operator % (doc_string docstr, token<T>& p) -{ - return p.doc(std::move(docstr)); -} -//--------------------------------------------------------- -template<class T> -inline T&& -operator % (doc_string docstr, token<T>&& p) -{ - return std::move(p.doc(std::move(docstr))); -} - -//--------------------------------------------------------- -template<class T> -inline T& -operator % (token<T>& p, doc_string docstr) -{ - return p.doc(std::move(docstr)); -} -//--------------------------------------------------------- -template<class T> -inline T&& -operator % (token<T>&& p, doc_string docstr) -{ - return std::move(p.doc(std::move(docstr))); -} - - - - -/*************************************************************************//** - * - * @brief sets documentation strings on a token - * - *****************************************************************************/ -template<class T> -inline T& -doc(doc_string docstr, token<T>& p) -{ - return p.doc(std::move(docstr)); -} -//--------------------------------------------------------- -template<class T> -inline T&& -doc(doc_string docstr, token<T>&& p) -{ - return std::move(p.doc(std::move(docstr))); -} - - - -} // namespace detail - - - -/*************************************************************************//** - * - * @brief contains parameter matching functions and function classes - * - *****************************************************************************/ -namespace match { - - -/*************************************************************************//** - * - * @brief predicate that is always true - * - *****************************************************************************/ -inline bool -any(const arg_string&) { return true; } - -/*************************************************************************//** - * - * @brief predicate that is always false - * - *****************************************************************************/ -inline bool -none(const arg_string&) { return false; } - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the argument string is non-empty string - * - *****************************************************************************/ -inline bool -nonempty(const arg_string& s) { - return !s.empty(); -} - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the argument is a non-empty - * string that consists only of alphanumeric characters - * - *****************************************************************************/ -inline bool -alphanumeric(const arg_string& s) { - if(s.empty()) return false; - return std::all_of(s.begin(), s.end(), [](char c) {return std::isalnum(c); }); -} - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the argument is a non-empty - * string that consists only of alphabetic characters - * - *****************************************************************************/ -inline bool -alphabetic(const arg_string& s) { - return std::all_of(s.begin(), s.end(), [](char c) {return std::isalpha(c); }); -} - - - -/*************************************************************************//** - * - * @brief predicate that returns the first substring match within the input - * string that rmeepresents a number - * (with at maximum one decimal point and digit separators) - * - *****************************************************************************/ -class numbers -{ -public: - explicit - numbers(char decimalPoint = '.', - char digitSeparator = ' ', - char exponentSeparator = 'e') - : - decpoint_{decimalPoint}, separator_{digitSeparator}, - exp_{exponentSeparator} - {} - - subrange operator () (const arg_string& s) const { - return str::first_number_match(s, separator_, decpoint_, exp_); - } - -private: - char decpoint_; - char separator_; - char exp_; -}; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the input string represents an integer - * (with optional digit separators) - * - *****************************************************************************/ -class integers { -public: - explicit - integers(char digitSeparator = ' '): separator_{digitSeparator} {} - - subrange operator () (const arg_string& s) const { - return str::first_integer_match(s, separator_); - } - -private: - char separator_; -}; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the input string represents - * a non-negative integer (with optional digit separators) - * - *****************************************************************************/ -class positive_integers { -public: - explicit - positive_integers(char digitSeparator = ' '): separator_{digitSeparator} {} - - subrange operator () (const arg_string& s) const { - auto match = str::first_integer_match(s, separator_); - if(!match) return subrange{}; - if(s[match.at()] == '-') return subrange{}; - return match; - } - -private: - char separator_; -}; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the input string - * contains a given substring - * - *****************************************************************************/ -class substring -{ -public: - explicit - substring(arg_string str): str_{std::move(str)} {} - - subrange operator () (const arg_string& s) const { - return str::substring_match(s, str_); - } - -private: - arg_string str_; -}; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the input string starts - * with a given prefix - * - *****************************************************************************/ -class prefix { -public: - explicit - prefix(arg_string p): prefix_{std::move(p)} {} - - bool operator () (const arg_string& s) const { - return s.find(prefix_) == 0; - } - -private: - arg_string prefix_; -}; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the input string does not start - * with a given prefix - * - *****************************************************************************/ -class prefix_not { -public: - explicit - prefix_not(arg_string p): prefix_{std::move(p)} {} - - bool operator () (const arg_string& s) const { - return s.find(prefix_) != 0; - } - -private: - arg_string prefix_; -}; - - -/** @brief alias for prefix_not */ -using noprefix = prefix_not; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the length of the input string - * is wihtin a given interval - * - *****************************************************************************/ -class length { -public: - explicit - length(std::size_t exact): - min_{exact}, max_{exact} - {} - - explicit - length(std::size_t min, std::size_t max): - min_{min}, max_{max} - {} - - bool operator () (const arg_string& s) const { - return s.size() >= min_ && s.size() <= max_; - } - -private: - std::size_t min_; - std::size_t max_; -}; - - -/*************************************************************************//** - * - * @brief makes function object that returns true if the input string has a - * given minimum length - * - *****************************************************************************/ -inline length min_length(std::size_t min) -{ - return length{min, arg_string::npos-1}; -} - -/*************************************************************************//** - * - * @brief makes function object that returns true if the input string is - * not longer than a given maximum length - * - *****************************************************************************/ -inline length max_length(std::size_t max) -{ - return length{0, max}; -} - - -} // namespace match - - - - - -/*************************************************************************//** - * - * @brief command line parameter that can match one or many arguments. - * - *****************************************************************************/ -class parameter : - public detail::token<parameter>, - public detail::action_provider<parameter> -{ - class predicate_adapter { - public: - explicit - predicate_adapter(match_predicate pred): match_{std::move(pred)} {} - - subrange operator () (const arg_string& arg) const { - return match_(arg) ? subrange{0,arg.size()} : subrange{}; - } - - private: - match_predicate match_; - }; - -public: - //--------------------------------------------------------------- - /** @brief makes default parameter, that will match nothing */ - parameter(): - flags_{}, - matcher_{predicate_adapter{match::none}}, - label_{}, required_{false} - {} - - /** @brief makes "flag" parameter */ - template<class... Strings> - explicit - parameter(arg_string str, Strings&&... strs): - flags_{}, - matcher_{predicate_adapter{match::none}}, - label_{}, required_{false} - { - add_flags(std::move(str), std::forward<Strings>(strs)...); - } - - /** @brief makes "flag" parameter from range of strings */ - explicit - parameter(const arg_list& flaglist): - flags_{}, - matcher_{predicate_adapter{match::none}}, - label_{}, required_{false} - { - add_flags(flaglist); - } - - //----------------------------------------------------- - /** @brief makes "value" parameter with custom match predicate - * (= yes/no matcher) - */ - explicit - parameter(match_predicate filter): - flags_{}, - matcher_{predicate_adapter{std::move(filter)}}, - label_{}, required_{false} - {} - - /** @brief makes "value" parameter with custom match function - * (= partial matcher) - */ - explicit - parameter(match_function filter): - flags_{}, - matcher_{std::move(filter)}, - label_{}, required_{false} - {} - - - //--------------------------------------------------------------- - /** @brief returns if a parameter is required */ - bool - required() const noexcept { - return required_; - } - - /** @brief determines if a parameter is required */ - parameter& - required(bool yes) noexcept { - required_ = yes; - return *this; - } - - - //--------------------------------------------------------------- - /** @brief returns parameter label; - * will be used for documentation, if flags are empty - */ - const doc_string& - label() const { - return label_; - } - - /** @brief sets parameter label; - * will be used for documentation, if flags are empty - */ - parameter& - label(const doc_string& lbl) { - label_ = lbl; - return *this; - } - - /** @brief sets parameter label; - * will be used for documentation, if flags are empty - */ - parameter& - label(doc_string&& lbl) { - label_ = lbl; - return *this; - } - - - //--------------------------------------------------------------- - /** @brief returns either longest matching prefix of 'arg' in any - * of the flags or the result of the custom match operation - */ - subrange - match(const arg_string& arg) const - { - if(arg.empty()) return subrange{}; - - if(flags_.empty()) { - return matcher_(arg); - } - else { - if(std::find(flags_.begin(), flags_.end(), arg) != flags_.end()) { - return subrange{0,arg.size()}; - } - return str::longest_prefix_match(arg, flags_); - } - } - - - //--------------------------------------------------------------- - /** @brief access range of flag strings */ - const arg_list& - flags() const noexcept { - return flags_; - } - - /** @brief access custom match operation */ - const match_function& - matcher() const noexcept { - return matcher_; - } - - - //--------------------------------------------------------------- - /** @brief prepend prefix to each flag */ - inline friend parameter& - with_prefix(const arg_string& prefix, parameter& p) - { - if(prefix.empty() || p.flags().empty()) return p; - - for(auto& f : p.flags_) { - if(f.find(prefix) != 0) f.insert(0, prefix); - } - return p; - } - - - /** @brief prepend prefix to each flag - */ - inline friend parameter& - with_prefixes_short_long( - const arg_string& shortpfx, const arg_string& longpfx, - parameter& p) - { - if(shortpfx.empty() && longpfx.empty()) return p; - if(p.flags().empty()) return p; - - for(auto& f : p.flags_) { - if(f.size() == 1) { - if(f.find(shortpfx) != 0) f.insert(0, shortpfx); - } else { - if(f.find(longpfx) != 0) f.insert(0, longpfx); - } - } - return p; - } - -private: - //--------------------------------------------------------------- - void add_flags(arg_string str) { - //empty flags are not allowed - str::remove_ws(str); - if(!str.empty()) flags_.push_back(std::move(str)); - } - - //--------------------------------------------------------------- - void add_flags(const arg_list& strs) { - if(strs.empty()) return; - flags_.reserve(flags_.size() + strs.size()); - for(const auto& s : strs) add_flags(s); - } - - template<class String1, class String2, class... Strings> - void - add_flags(String1&& s1, String2&& s2, Strings&&... ss) { - flags_.reserve(2 + sizeof...(ss)); - add_flags(std::forward<String1>(s1)); - add_flags(std::forward<String2>(s2), std::forward<Strings>(ss)...); - } - - arg_list flags_; - match_function matcher_; - doc_string label_; - bool required_ = false; -}; - - - - -/*************************************************************************//** - * - * @brief makes required non-blocking exact match parameter - * - *****************************************************************************/ -template<class String, class... Strings> -inline parameter -command(String&& flag, Strings&&... flags) -{ - return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...} - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required non-blocking exact match parameter - * - *****************************************************************************/ -template<class String, class... Strings> -inline parameter -required(String&& flag, Strings&&... flags) -{ - return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...} - .required(true).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, non-blocking exact match parameter - * - *****************************************************************************/ -template<class String, class... Strings> -inline parameter -option(String&& flag, Strings&&... flags) -{ - return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...} - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template<class... Targets> -inline parameter -value(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(true).blocking(true).repeatable(false); -} - -template<class Filter, class... Targets, class = typename std::enable_if< - traits::is_callable<Filter,bool(const char*)>::value || - traits::is_callable<Filter,subrange(const char*)>::value>::type> -inline parameter -value(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward<Filter>(filter)} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template<class... Targets> -inline parameter -values(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(true).blocking(true).repeatable(true); -} - -template<class Filter, class... Targets, class = typename std::enable_if< - traits::is_callable<Filter,bool(const char*)>::value || - traits::is_callable<Filter,subrange(const char*)>::value>::type> -inline parameter -values(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward<Filter>(filter)} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template<class... Targets> -inline parameter -opt_value(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(false).blocking(false).repeatable(false); -} - -template<class Filter, class... Targets, class = typename std::enable_if< - traits::is_callable<Filter,bool(const char*)>::value || - traits::is_callable<Filter,subrange(const char*)>::value>::type> -inline parameter -opt_value(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward<Filter>(filter)} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template<class... Targets> -inline parameter -opt_values(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(false).blocking(false).repeatable(true); -} - -template<class Filter, class... Targets, class = typename std::enable_if< - traits::is_callable<Filter,bool(const char*)>::value || - traits::is_callable<Filter,subrange(const char*)>::value>::type> -inline parameter -opt_values(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward<Filter>(filter)} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template<class... Targets> -inline parameter -word(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template<class... Targets> -inline parameter -words(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template<class... Targets> -inline parameter -opt_word(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template<class... Targets> -inline parameter -opt_words(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template<class... Targets> -inline parameter -number(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template<class... Targets> -inline parameter -numbers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template<class... Targets> -inline parameter -opt_number(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template<class... Targets> -inline parameter -opt_numbers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template<class... Targets> -inline parameter -integer(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template<class... Targets> -inline parameter -integers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template<class... Targets> -inline parameter -opt_integer(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template<class... Targets> -inline parameter -opt_integers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward<Targets>(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes catch-all value parameter - * - *****************************************************************************/ -template<class... Targets> -inline parameter -any_other(Targets&&... tgts) -{ - return parameter{match::any} - .target(std::forward<Targets>(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - - -/*************************************************************************//** - * - * @brief group of parameters and/or other groups; - * can be configured to act as a group of alternatives (exclusive match) - * - *****************************************************************************/ -class group : - public detail::token<group> -{ - //--------------------------------------------------------------- - /** - * @brief tagged union type that either stores a parameter or a group - * and provides a common interface to them - * could be replaced by std::variant in the future - * - * Note to future self: do NOT try again to do this with - * dynamic polymorphism; there are a couple of - * nasty problems associated with it and the implementation - * becomes bloated and needlessly complicated. - */ - template<class Param, class Group> - struct child_t { - enum class type : char {param, group}; - public: - - explicit - child_t(const Param& v) : m_{v}, type_{type::param} {} - child_t( Param&& v) noexcept : m_{std::move(v)}, type_{type::param} {} - - explicit - child_t(const Group& g) : m_{g}, type_{type::group} {} - child_t( Group&& g) noexcept : m_{std::move(g)}, type_{type::group} {} - - child_t(const child_t& src): type_{src.type_} { - switch(type_) { - default: - case type::param: new(&m_)data{src.m_.param}; break; - case type::group: new(&m_)data{src.m_.group}; break; - } - } - - child_t(child_t&& src) noexcept : type_{src.type_} { - switch(type_) { - default: - case type::param: new(&m_)data{std::move(src.m_.param)}; break; - case type::group: new(&m_)data{std::move(src.m_.group)}; break; - } - } - - child_t& operator = (const child_t& src) { - destroy_content(); - type_ = src.type_; - switch(type_) { - default: - case type::param: new(&m_)data{src.m_.param}; break; - case type::group: new(&m_)data{src.m_.group}; break; - } - return *this; - } - - child_t& operator = (child_t&& src) noexcept { - destroy_content(); - type_ = src.type_; - switch(type_) { - default: - case type::param: new(&m_)data{std::move(src.m_.param)}; break; - case type::group: new(&m_)data{std::move(src.m_.group)}; break; - } - return *this; - } - - ~child_t() { - destroy_content(); - } - - const doc_string& - doc() const noexcept { - switch(type_) { - default: - case type::param: return m_.param.doc(); - case type::group: return m_.group.doc(); - } - } - - bool blocking() const noexcept { - switch(type_) { - case type::param: return m_.param.blocking(); - case type::group: return m_.group.blocking(); - default: return false; - } - } - bool repeatable() const noexcept { - switch(type_) { - case type::param: return m_.param.repeatable(); - case type::group: return m_.group.repeatable(); - default: return false; - } - } - bool required() const noexcept { - switch(type_) { - case type::param: return m_.param.required(); - case type::group: - return (m_.group.exclusive() && m_.group.all_required() ) || - (!m_.group.exclusive() && m_.group.any_required() ); - default: return false; - } - } - bool exclusive() const noexcept { - switch(type_) { - case type::group: return m_.group.exclusive(); - case type::param: - default: return false; - } - } - std::size_t param_count() const noexcept { - switch(type_) { - case type::group: return m_.group.param_count(); - case type::param: - default: return std::size_t(1); - } - } - std::size_t depth() const noexcept { - switch(type_) { - case type::group: return m_.group.depth(); - case type::param: - default: return std::size_t(0); - } - } - - void execute_actions(const arg_string& arg) const { - switch(type_) { - default: - case type::group: return; - case type::param: m_.param.execute_actions(arg); break; - } - - } - - void notify_repeated(arg_index idx) const { - switch(type_) { - default: - case type::group: return; - case type::param: m_.param.notify_repeated(idx); break; - } - } - void notify_missing(arg_index idx) const { - switch(type_) { - default: - case type::group: return; - case type::param: m_.param.notify_missing(idx); break; - } - } - void notify_blocked(arg_index idx) const { - switch(type_) { - default: - case type::group: return; - case type::param: m_.param.notify_blocked(idx); break; - } - } - void notify_conflict(arg_index idx) const { - switch(type_) { - default: - case type::group: return; - case type::param: m_.param.notify_conflict(idx); break; - } - } - - bool is_param() const noexcept { return type_ == type::param; } - bool is_group() const noexcept { return type_ == type::group; } - - Param& as_param() noexcept { return m_.param; } - Group& as_group() noexcept { return m_.group; } - - const Param& as_param() const noexcept { return m_.param; } - const Group& as_group() const noexcept { return m_.group; } - - private: - void destroy_content() { - switch(type_) { - default: - case type::param: m_.param.~Param(); break; - case type::group: m_.group.~Group(); break; - } - } - - union data { - data() {} - - data(const Param& v) : param{v} {} - data( Param&& v) noexcept : param{std::move(v)} {} - - data(const Group& g) : group{g} {} - data( Group&& g) noexcept : group{std::move(g)} {} - ~data() {} - - Param param; - Group group; - }; - - data m_; - type type_; - }; - - -public: - //--------------------------------------------------------------- - using child = child_t<parameter,group>; - using value_type = child; - -private: - using children_store = std::vector<child>; - -public: - using const_iterator = children_store::const_iterator; - using iterator = children_store::iterator; - using size_type = children_store::size_type; - - - //--------------------------------------------------------------- - /** - * @brief recursively iterates over all nodes - */ - class depth_first_traverser - { - public: - //----------------------------------------------------- - struct context { - context() = default; - context(const group& p): - parent{&p}, cur{p.begin()}, end{p.end()} - {} - const group* parent = nullptr; - const_iterator cur; - const_iterator end; - }; - using context_list = std::vector<context>; - - //----------------------------------------------------- - class memento { - friend class depth_first_traverser; - int level_; - context context_; - public: - int level() const noexcept { return level_; } - const child* param() const noexcept { return &(*context_.cur); } - }; - - depth_first_traverser() = default; - - explicit - depth_first_traverser(const group& cur): stack_{} { - if(!cur.empty()) stack_.emplace_back(cur); - } - - explicit operator bool() const noexcept { - return !stack_.empty(); - } - - int level() const noexcept { - return int(stack_.size()); - } - - bool is_first_in_group() const noexcept { - if(stack_.empty()) return false; - return (stack_.back().cur == stack_.back().parent->begin()); - } - - bool is_last_in_group() const noexcept { - if(stack_.empty()) return false; - return (stack_.back().cur+1 == stack_.back().end); - } - - bool is_last_in_path() const noexcept { - if(stack_.empty()) return false; - for(const auto& t : stack_) { - if(t.cur+1 != t.end) return false; - } - const auto& top = stack_.back(); - //if we have to descend into group on next ++ => not last in path - if(top.cur->is_group()) return false; - return true; - } - - /** @brief inside a group of alternatives >= minlevel */ - bool is_alternative(int minlevel = 0) const noexcept { - if(stack_.empty()) return false; - if(minlevel > 0) minlevel -= 1; - if(minlevel >= int(stack_.size())) return false; - return std::any_of(stack_.begin() + minlevel, stack_.end(), - [](const context& c) { return c.parent->exclusive(); }); - } - - /** @brief repeatable or inside a repeatable group >= minlevel */ - bool is_repeatable(int minlevel = 0) const noexcept { - if(stack_.empty()) return false; - if(stack_.back().cur->repeatable()) return true; - if(minlevel > 0) minlevel -= 1; - if(minlevel >= int(stack_.size())) return false; - return std::any_of(stack_.begin() + minlevel, stack_.end(), - [](const context& c) { return c.parent->repeatable(); }); - } - /** @brief inside group with joinable flags */ - bool joinable() const noexcept { - if(stack_.empty()) return false; - return std::any_of(stack_.begin(), stack_.end(), - [](const context& c) { return c.parent->joinable(); }); - } - - const context_list& - stack() const { - return stack_; - } - - /** @brief innermost repeat group */ - const group* - repeat_group() const noexcept { - auto i = std::find_if(stack_.rbegin(), stack_.rend(), - [](const context& c) { return c.parent->repeatable(); }); - - return i != stack_.rend() ? i->parent : nullptr; - } - - /** @brief outermost join group */ - const group* - join_group() const noexcept { - auto i = std::find_if(stack_.begin(), stack_.end(), - [](const context& c) { return c.parent->joinable(); }); - return i != stack_.end() ? i->parent : nullptr; - } - - const group* root() const noexcept { - return stack_.empty() ? nullptr : stack_.front().parent; - } - - /** @brief common flag prefix of all flags in current group */ - arg_string common_flag_prefix() const noexcept { - if(stack_.empty()) return ""; - auto g = join_group(); - return g ? g->common_flag_prefix() : arg_string(""); - } - - const child& - operator * () const noexcept { - return *stack_.back().cur; - } - - const child* - operator -> () const noexcept { - return &(*stack_.back().cur); - } - - const group& - parent() const noexcept { - return *(stack_.back().parent); - } - - - /** @brief go to next element of depth first search */ - depth_first_traverser& - operator ++ () { - if(stack_.empty()) return *this; - //at group -> decend into group - if(stack_.back().cur->is_group()) { - stack_.emplace_back(stack_.back().cur->as_group()); - } - else { - next_sibling(); - } - return *this; - } - - /** @brief go to next sibling of current */ - depth_first_traverser& - next_sibling() { - if(stack_.empty()) return *this; - ++stack_.back().cur; - //at the end of current group? - while(stack_.back().cur == stack_.back().end) { - //go to parent - stack_.pop_back(); - if(stack_.empty()) return *this; - //go to next sibling in parent - ++stack_.back().cur; - } - return *this; - } - - /** @brief go to next position after siblings of current */ - depth_first_traverser& - next_after_siblings() { - if(stack_.empty()) return *this; - stack_.back().cur = stack_.back().end-1; - next_sibling(); - return *this; - } - - /** @brief skips to next alternative in innermost group - */ - depth_first_traverser& - next_alternative() { - if(stack_.empty()) return *this; - - //find first exclusive group (from the top of the stack!) - auto i = std::find_if(stack_.rbegin(), stack_.rend(), - [](const context& c) { return c.parent->exclusive(); }); - if(i == stack_.rend()) return *this; - - stack_.erase(i.base(), stack_.end()); - next_sibling(); - return *this; - } - - /** - * @brief - */ - depth_first_traverser& - back_to_parent() { - if(stack_.empty()) return *this; - stack_.pop_back(); - return *this; - } - - /** @brief don't visit next siblings, go back to parent on next ++ - * note: renders siblings unreachable for *this - **/ - depth_first_traverser& - skip_siblings() { - if(stack_.empty()) return *this; - //future increments won't visit subsequent siblings: - stack_.back().end = stack_.back().cur+1; - return *this; - } - - /** @brief skips all other alternatives in surrounding exclusive groups - * on next ++ - * note: renders alternatives unreachable for *this - */ - depth_first_traverser& - skip_alternatives() { - if(stack_.empty()) return *this; - - //exclude all other alternatives in surrounding groups - //by making their current position the last one - for(auto& c : stack_) { - if(c.parent && c.parent->exclusive() && c.cur < c.end) - c.end = c.cur+1; - } - - return *this; - } - - void invalidate() { - stack_.clear(); - } - - inline friend bool operator == (const depth_first_traverser& a, - const depth_first_traverser& b) - { - if(a.stack_.empty() || b.stack_.empty()) return false; - - //parents not the same -> different position - if(a.stack_.back().parent != b.stack_.back().parent) return false; - - bool aEnd = a.stack_.back().cur == a.stack_.back().end; - bool bEnd = b.stack_.back().cur == b.stack_.back().end; - //either both at the end of the same parent => same position - if(aEnd && bEnd) return true; - //or only one at the end => not at the same position - if(aEnd || bEnd) return false; - return std::addressof(*a.stack_.back().cur) == - std::addressof(*b.stack_.back().cur); - } - inline friend bool operator != (const depth_first_traverser& a, - const depth_first_traverser& b) - { - return !(a == b); - } - - memento - undo_point() const { - memento m; - m.level_ = int(stack_.size()); - if(!stack_.empty()) m.context_ = stack_.back(); - return m; - } - - void undo(const memento& m) { - if(m.level_ < 1) return; - if(m.level_ <= int(stack_.size())) { - stack_.erase(stack_.begin() + m.level_, stack_.end()); - stack_.back() = m.context_; - } - else if(stack_.empty() && m.level_ == 1) { - stack_.push_back(m.context_); - } - } - - private: - context_list stack_; - }; - - - //--------------------------------------------------------------- - group() = default; - - template<class Param, class... Params> - explicit - group(doc_string docstr, Param param, Params... params): - children_{}, exclusive_{false}, joinable_{false}, scoped_{true} - { - doc(std::move(docstr)); - push_back(std::move(param), std::move(params)...); - } - - template<class... Params> - explicit - group(parameter param, Params... params): - children_{}, exclusive_{false}, joinable_{false}, scoped_{true} - { - push_back(std::move(param), std::move(params)...); - } - - template<class P2, class... Ps> - explicit - group(group p1, P2 p2, Ps... ps): - children_{}, exclusive_{false}, joinable_{false}, scoped_{true} - { - push_back(std::move(p1), std::move(p2), std::move(ps)...); - } - - - //----------------------------------------------------- - group(const group&) = default; - group(group&&) = default; - - - //--------------------------------------------------------------- - group& operator = (const group&) = default; - group& operator = (group&&) = default; - - - //--------------------------------------------------------------- - /** @brief determines if a command line argument can be matched by a - * combination of (partial) matches through any number of children - */ - group& joinable(bool yes) { - joinable_ = yes; - return *this; - } - - /** @brief returns if a command line argument can be matched by a - * combination of (partial) matches through any number of children - */ - bool joinable() const noexcept { - return joinable_; - } - - - //--------------------------------------------------------------- - /** @brief turns explicit scoping on or off - * operators , & | and other combinating functions will - * not merge groups that are marked as scoped - */ - group& scoped(bool yes) { - scoped_ = yes; - return *this; - } - - /** @brief returns true if operators , & | and other combinating functions - * will merge groups and false otherwise - */ - bool scoped() const noexcept - { - return scoped_; - } - - - //--------------------------------------------------------------- - /** @brief determines if children are mutually exclusive alternatives */ - group& exclusive(bool yes) { - exclusive_ = yes; - return *this; - } - /** @brief returns if children are mutually exclusive alternatives */ - bool exclusive() const noexcept { - return exclusive_; - } - - - //--------------------------------------------------------------- - /** @brief returns true, if any child is required to match */ - bool any_required() const - { - return std::any_of(children_.begin(), children_.end(), - [](const child& n){ return n.required(); }); - } - /** @brief returns true, if all children are required to match */ - bool all_required() const - { - return std::all_of(children_.begin(), children_.end(), - [](const child& n){ return n.required(); }); - } - - - //--------------------------------------------------------------- - /** @brief returns true if any child is optional (=non-required) */ - bool any_optional() const { - return !all_required(); - } - /** @brief returns true if all children are optional (=non-required) */ - bool all_optional() const { - return !any_required(); - } - - - //--------------------------------------------------------------- - /** @brief returns if the entire group is blocking / positional */ - bool blocking() const noexcept { - return token<group>::blocking() || (exclusive() && all_blocking()); - } - //----------------------------------------------------- - /** @brief determines if the entire group is blocking / positional */ - group& blocking(bool yes) { - return token<group>::blocking(yes); - } - - //--------------------------------------------------------------- - /** @brief returns true if any child is blocking */ - bool any_blocking() const - { - return std::any_of(children_.begin(), children_.end(), - [](const child& n){ return n.blocking(); }); - } - //--------------------------------------------------------------- - /** @brief returns true if all children is blocking */ - bool all_blocking() const - { - return std::all_of(children_.begin(), children_.end(), - [](const child& n){ return n.blocking(); }); - } - - - //--------------------------------------------------------------- - /** @brief returns if any child is a value parameter (recursive) */ - bool any_flagless() const - { - return std::any_of(children_.begin(), children_.end(), - [](const child& p){ - return p.is_param() && p.as_param().flags().empty(); - }); - } - /** @brief returns if all children are value parameters (recursive) */ - bool all_flagless() const - { - return std::all_of(children_.begin(), children_.end(), - [](const child& p){ - return p.is_param() && p.as_param().flags().empty(); - }); - } - - - //--------------------------------------------------------------- - /** @brief adds child parameter at the end */ - group& - push_back(const parameter& v) { - children_.emplace_back(v); - return *this; - } - //----------------------------------------------------- - /** @brief adds child parameter at the end */ - group& - push_back(parameter&& v) { - children_.emplace_back(std::move(v)); - return *this; - } - //----------------------------------------------------- - /** @brief adds child group at the end */ - group& - push_back(const group& g) { - children_.emplace_back(g); - return *this; - } - //----------------------------------------------------- - /** @brief adds child group at the end */ - group& - push_back(group&& g) { - children_.emplace_back(std::move(g)); - return *this; - } - - - //----------------------------------------------------- - /** @brief adds children (groups and/or parameters) */ - template<class Param1, class Param2, class... Params> - group& - push_back(Param1&& param1, Param2&& param2, Params&&... params) - { - children_.reserve(children_.size() + 2 + sizeof...(params)); - push_back(std::forward<Param1>(param1)); - push_back(std::forward<Param2>(param2), std::forward<Params>(params)...); - return *this; - } - - - //--------------------------------------------------------------- - /** @brief adds child parameter at the beginning */ - group& - push_front(const parameter& v) { - children_.emplace(children_.begin(), v); - return *this; - } - //----------------------------------------------------- - /** @brief adds child parameter at the beginning */ - group& - push_front(parameter&& v) { - children_.emplace(children_.begin(), std::move(v)); - return *this; - } - //----------------------------------------------------- - /** @brief adds child group at the beginning */ - group& - push_front(const group& g) { - children_.emplace(children_.begin(), g); - return *this; - } - //----------------------------------------------------- - /** @brief adds child group at the beginning */ - group& - push_front(group&& g) { - children_.emplace(children_.begin(), std::move(g)); - return *this; - } - - - //--------------------------------------------------------------- - /** @brief adds all children of other group at the end */ - group& - merge(group&& g) - { - children_.insert(children_.end(), - std::make_move_iterator(g.begin()), - std::make_move_iterator(g.end())); - return *this; - } - //----------------------------------------------------- - /** @brief adds all children of several other groups at the end */ - template<class... Groups> - group& - merge(group&& g1, group&& g2, Groups&&... gs) - { - merge(std::move(g1)); - merge(std::move(g2), std::forward<Groups>(gs)...); - return *this; - } - - - //--------------------------------------------------------------- - /** @brief indexed, nutable access to child */ - child& operator [] (size_type index) noexcept { - return children_[index]; - } - /** @brief indexed, non-nutable access to child */ - const child& operator [] (size_type index) const noexcept { - return children_[index]; - } - - //--------------------------------------------------------------- - /** @brief mutable access to first child */ - child& front() noexcept { return children_.front(); } - /** @brief non-mutable access to first child */ - const child& front() const noexcept { return children_.front(); } - //----------------------------------------------------- - /** @brief mutable access to last child */ - child& back() noexcept { return children_.back(); } - /** @brief non-mutable access to last child */ - const child& back() const noexcept { return children_.back(); } - - - //--------------------------------------------------------------- - /** @brief returns true, if group has no children, false otherwise */ - bool empty() const noexcept { return children_.empty(); } - - /** @brief returns number of children */ - size_type size() const noexcept { return children_.size(); } - - /** @brief returns number of nested levels; 1 for a flat group */ - size_type depth() const { - size_type n = 0; - for(const auto& c : children_) { - auto l = 1 + c.depth(); - if(l > n) n = l; - } - return n; - } - - - //--------------------------------------------------------------- - /** @brief returns mutating iterator to position of first element */ - iterator begin() noexcept { return children_.begin(); } - /** @brief returns non-mutating iterator to position of first element */ - const_iterator begin() const noexcept { return children_.begin(); } - /** @brief returns non-mutating iterator to position of first element */ - const_iterator cbegin() const noexcept { return children_.begin(); } - - /** @brief returns mutating iterator to position one past the last element */ - iterator end() noexcept { return children_.end(); } - /** @brief returns non-mutating iterator to position one past the last element */ - const_iterator end() const noexcept { return children_.end(); } - /** @brief returns non-mutating iterator to position one past the last element */ - const_iterator cend() const noexcept { return children_.end(); } - - - //--------------------------------------------------------------- - /** @brief returns augmented iterator for depth first searches - * @details taverser knows end of iteration and can skip over children - */ - depth_first_traverser - begin_dfs() const noexcept { - return depth_first_traverser{*this}; - } - - - //--------------------------------------------------------------- - /** @brief returns recursive parameter count */ - size_type param_count() const { - size_type c = 0; - for(const auto& n : children_) { - c += n.param_count(); - } - return c; - } - - - //--------------------------------------------------------------- - /** @brief returns range of all flags (recursive) */ - arg_list all_flags() const - { - std::vector<arg_string> all; - gather_flags(children_, all); - return all; - } - - /** @brief returns true, if no flag occurs as true - * prefix of any other flag (identical flags will be ignored) */ - bool flags_are_prefix_free() const - { - const auto fs = all_flags(); - - using std::begin; using std::end; - for(auto i = begin(fs), e = end(fs); i != e; ++i) { - if(!i->empty()) { - for(auto j = i+1; j != e; ++j) { - if(!j->empty() && *i != *j) { - if(i->find(*j) == 0) return false; - if(j->find(*i) == 0) return false; - } - } - } - } - - return true; - } - - - //--------------------------------------------------------------- - /** @brief returns longest common prefix of all flags */ - arg_string common_flag_prefix() const - { - arg_list prefixes; - gather_prefixes(children_, prefixes); - return str::longest_common_prefix(prefixes); - } - - -private: - //--------------------------------------------------------------- - static void - gather_flags(const children_store& nodes, arg_list& all) - { - for(const auto& p : nodes) { - if(p.is_group()) { - gather_flags(p.as_group().children_, all); - } - else { - const auto& pf = p.as_param().flags(); - using std::begin; - using std::end; - if(!pf.empty()) all.insert(end(all), begin(pf), end(pf)); - } - } - } - //--------------------------------------------------------------- - static void - gather_prefixes(const children_store& nodes, arg_list& all) - { - for(const auto& p : nodes) { - if(p.is_group()) { - gather_prefixes(p.as_group().children_, all); - } - else if(!p.as_param().flags().empty()) { - auto pfx = str::longest_common_prefix(p.as_param().flags()); - if(!pfx.empty()) all.push_back(std::move(pfx)); - } - } - } - - //--------------------------------------------------------------- - children_store children_; - bool exclusive_ = false; - bool joinable_ = false; - bool scoped_ = false; -}; - - - -/*************************************************************************//** - * - * @brief group or parameter - * - *****************************************************************************/ -using pattern = group::child; - - - -/*************************************************************************//** - * - * @brief makes a group of parameters and/or groups - * - *****************************************************************************/ -inline group -operator , (parameter a, parameter b) -{ - return group{std::move(a), std::move(b)}.scoped(false); -} - -//--------------------------------------------------------- -inline group -operator , (parameter a, group b) -{ - return !b.scoped() && !b.blocking() && !b.exclusive() && !b.repeatable() - && !b.joinable() && (b.doc().empty() || b.doc() == a.doc()) - ? b.push_front(std::move(a)) - : group{std::move(a), std::move(b)}.scoped(false); -} - -//--------------------------------------------------------- -inline group -operator , (group a, parameter b) -{ - return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable() - && !a.joinable() && (a.doc().empty() || a.doc() == b.doc()) - ? a.push_back(std::move(b)) - : group{std::move(a), std::move(b)}.scoped(false); -} - -//--------------------------------------------------------- -inline group -operator , (group a, group b) -{ - return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable() - && !a.joinable() && (a.doc().empty() || a.doc() == b.doc()) - ? a.push_back(std::move(b)) - : group{std::move(a), std::move(b)}.scoped(false); -} - - - -/*************************************************************************//** - * - * @brief makes a group of alternative parameters or groups - * - *****************************************************************************/ -template<class Param, class... Params> -inline group -one_of(Param param, Params... params) -{ - return group{std::move(param), std::move(params)...}.exclusive(true); -} - - -/*************************************************************************//** - * - * @brief makes a group of alternative parameters or groups - * - *****************************************************************************/ -inline group -operator | (parameter a, parameter b) -{ - return group{std::move(a), std::move(b)}.scoped(false).exclusive(true); -} - -//------------------------------------------------------------------- -inline group -operator | (parameter a, group b) -{ - return !b.scoped() && !b.blocking() && b.exclusive() && !b.repeatable() - && !b.joinable() - && (b.doc().empty() || b.doc() == a.doc()) - ? b.push_front(std::move(a)) - : group{std::move(a), std::move(b)}.scoped(false).exclusive(true); -} - -//------------------------------------------------------------------- -inline group -operator | (group a, parameter b) -{ - return !a.scoped() && a.exclusive() && !a.repeatable() && !a.joinable() - && a.blocking() == b.blocking() - && (a.doc().empty() || a.doc() == b.doc()) - ? a.push_back(std::move(b)) - : group{std::move(a), std::move(b)}.scoped(false).exclusive(true); -} - -inline group -operator | (group a, group b) -{ - return !a.scoped() && a.exclusive() &&!a.repeatable() && !a.joinable() - && a.blocking() == b.blocking() - && (a.doc().empty() || a.doc() == b.doc()) - ? a.push_back(std::move(b)) - : group{std::move(a), std::move(b)}.scoped(false).exclusive(true); -} - - - -namespace detail { - -inline void set_blocking(bool) {} - -template<class P, class... Ps> -void set_blocking(bool yes, P& p, Ps&... ps) { - p.blocking(yes); - set_blocking(yes, ps...); -} - -} // namespace detail - - -/*************************************************************************//** - * - * @brief makes a parameter/group sequence by making all input objects blocking - * - *****************************************************************************/ -template<class Param, class... Params> -inline group -in_sequence(Param param, Params... params) -{ - detail::set_blocking(true, param, params...); - return group{std::move(param), std::move(params)...}.scoped(true); -} - - -/*************************************************************************//** - * - * @brief makes a parameter/group sequence by making all input objects blocking - * - *****************************************************************************/ -inline group -operator & (parameter a, parameter b) -{ - a.blocking(true); - b.blocking(true); - return group{std::move(a), std::move(b)}.scoped(true); -} - -//--------------------------------------------------------- -inline group -operator & (parameter a, group b) -{ - a.blocking(true); - return group{std::move(a), std::move(b)}.scoped(true); -} - -//--------------------------------------------------------- -inline group -operator & (group a, parameter b) -{ - b.blocking(true); - if(a.all_blocking() && !a.exclusive() && !a.repeatable() && !a.joinable() - && (a.doc().empty() || a.doc() == b.doc())) - { - return a.push_back(std::move(b)); - } - else { - if(!a.all_blocking()) a.blocking(true); - return group{std::move(a), std::move(b)}.scoped(true); - } -} - -inline group -operator & (group a, group b) -{ - if(!b.all_blocking()) b.blocking(true); - if(a.all_blocking() && !a.exclusive() && !a.repeatable() - && !a.joinable() && (a.doc().empty() || a.doc() == b.doc())) - { - return a.push_back(std::move(b)); - } - else { - if(!a.all_blocking()) a.blocking(true); - return group{std::move(a), std::move(b)}.scoped(true); - } -} - - - -/*************************************************************************//** - * - * @brief makes a group of parameters and/or groups - * where all single char flag params ("-a", "b", ...) are joinable - * - *****************************************************************************/ -inline group& -joinable(group& param) { - return param.joinable(true); -} - -inline group&& -joinable(group&& param) { - return std::move(param.joinable(true)); -} - -//------------------------------------------------------------------- -template<class... Params> -inline group -joinable(parameter param, Params... params) -{ - return group{std::move(param), std::move(params)...}.joinable(true); -} - -template<class P2, class... Ps> -inline group -joinable(group p1, P2 p2, Ps... ps) -{ - return group{std::move(p1), std::move(p2), std::move(ps)...}.joinable(true); -} - -template<class Param, class... Params> -inline group -joinable(doc_string docstr, Param param, Params... params) -{ - return group{std::move(param), std::move(params)...} - .joinable(true).doc(std::move(docstr)); -} - - - -/*************************************************************************//** - * - * @brief makes a repeatable copy of a parameter - * - *****************************************************************************/ -inline parameter -repeatable(parameter p) { - return p.repeatable(true); -} - -/*************************************************************************//** - * - * @brief makes a repeatable copy of a group - * - *****************************************************************************/ -inline group -repeatable(group g) { - return g.repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes a group of parameters and/or groups - * that is repeatable as a whole - * Note that a repeatable group consisting entirely of non-blocking - * children is equivalent to a non-repeatable group of - * repeatable children. - * - *****************************************************************************/ -template<class P2, class... Ps> -inline group -repeatable(parameter p1, P2 p2, Ps... ps) -{ - return group{std::move(p1), std::move(p2), - std::move(ps)...}.repeatable(true); -} - -template<class P2, class... Ps> -inline group -repeatable(group p1, P2 p2, Ps... ps) -{ - return group{std::move(p1), std::move(p2), - std::move(ps)...}.repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief recursively prepends a prefix to all flags - * - *****************************************************************************/ -inline parameter&& -with_prefix(const arg_string& prefix, parameter&& p) { - return std::move(with_prefix(prefix, p)); -} - - -//------------------------------------------------------------------- -inline group& -with_prefix(const arg_string& prefix, group& params) -{ - for(auto& p : params) { - if(p.is_group()) { - with_prefix(prefix, p.as_group()); - } else { - with_prefix(prefix, p.as_param()); - } - } - return params; -} - - -inline group&& -with_prefix(const arg_string& prefix, group&& params) -{ - return std::move(with_prefix(prefix, params)); -} - - -template<class Param, class... Params> -inline group -with_prefix(arg_string prefix, Param&& param, Params&&... params) -{ - return with_prefix(prefix, group{std::forward<Param>(param), - std::forward<Params>(params)...}); -} - - - -/*************************************************************************//** - * - * @brief recursively prepends a prefix to all flags - * - * @param shortpfx : used for single-letter flags - * @param longpfx : used for flags with length > 1 - * - *****************************************************************************/ -inline parameter&& -with_prefixes_short_long(const arg_string& shortpfx, const arg_string& longpfx, - parameter&& p) -{ - return std::move(with_prefixes_short_long(shortpfx, longpfx, p)); -} - - -//------------------------------------------------------------------- -inline group& -with_prefixes_short_long(const arg_string& shortFlagPrefix, - const arg_string& longFlagPrefix, - group& params) -{ - for(auto& p : params) { - if(p.is_group()) { - with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_group()); - } else { - with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_param()); - } - } - return params; -} - - -inline group&& -with_prefixes_short_long(const arg_string& shortFlagPrefix, - const arg_string& longFlagPrefix, - group&& params) -{ - return std::move(with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, - params)); -} - - -template<class Param, class... Params> -inline group -with_prefixes_short_long(const arg_string& shortFlagPrefix, - const arg_string& longFlagPrefix, - Param&& param, Params&&... params) -{ - return with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, - group{std::forward<Param>(param), - std::forward<Params>(params)...}); -} - - - - - - - - -/*************************************************************************//** - * - * @brief parsing implementation details - * - *****************************************************************************/ -namespace detail { - - -/*************************************************************************//** - * - * @brief DFS traverser that keeps track of 'scopes' - * scope = all parameters that are either bounded by - * two blocking parameters on the same depth level - * or the beginning/end of the outermost group - * - *****************************************************************************/ -class scoped_dfs_traverser -{ -public: - using dfs_traverser = group::depth_first_traverser; - - scoped_dfs_traverser() = default; - - explicit - scoped_dfs_traverser(const group& g): - pos_{g}, lastMatch_{}, posAfterLastMatch_{}, scopes_{}, - curMatched_{false}, ignoreBlocks_{false}, - repeatGroupStarted_{false}, repeatGroupContinues_{false} - {} - - const dfs_traverser& base() const noexcept { return pos_; } - const dfs_traverser& last_match() const noexcept { return lastMatch_; } - - const group& parent() const noexcept { return pos_.parent(); } - const group* repeat_group() const noexcept { return pos_.repeat_group(); } - const group* join_group() const noexcept { return pos_.join_group(); } - - const pattern* operator ->() const noexcept { return pos_.operator->(); } - const pattern& operator *() const noexcept { return *pos_; } - - const pattern* ptr() const noexcept { return pos_.operator->(); } - - explicit operator bool() const noexcept { return bool(pos_); } - - bool joinable() const noexcept { return pos_.joinable(); } - arg_string common_flag_prefix() const { return pos_.common_flag_prefix(); } - - void ignore_blocking(bool yes) { ignoreBlocks_ = yes; } - - void invalidate() { pos_.invalidate(); curMatched_ = false; } - bool matched() const noexcept { return curMatched_; } - - bool start_of_repeat_group() const noexcept { return repeatGroupStarted_; } - - //----------------------------------------------------- - scoped_dfs_traverser& - next_sibling() { pos_.next_sibling(); return *this; } - - scoped_dfs_traverser& - next_alternative() { pos_.next_alternative(); return *this; } - - scoped_dfs_traverser& - next_after_siblings() { pos_.next_after_siblings(); return *this; } - - //----------------------------------------------------- - scoped_dfs_traverser& - operator ++ () - { - if(!pos_) return *this; - - if(pos_.is_last_in_path()) { - return_to_outermost_scope(); - return *this; - } - - //current pattern can block if it didn't match already - if(!ignoreBlocks_ && !matched()) { - //current group can block if we didn't have any match in it - if(pos_.is_last_in_group() && pos_.parent().blocking() - && (!posAfterLastMatch_ || &(posAfterLastMatch_.parent()) != &(pos_.parent()))) - { - //ascend to parent's level - ++pos_; - //skip all siblings of parent group - pos_.next_after_siblings(); - if(!pos_) return_to_outermost_scope(); - } - else if(pos_->blocking() && !pos_->is_group()) { - if(pos_.parent().exclusive()) { //is_alternative(pos_.level())) { - pos_.next_alternative(); - } else { - //no match => skip siblings of blocking param - pos_.next_after_siblings(); - } - if(!pos_) return_to_outermost_scope(); - } else { - ++pos_; - } - } else { - ++pos_; - } - check_left_scope(); - return *this; - } - - //----------------------------------------------------- - void next_after_match(scoped_dfs_traverser match) - { - if(!match || ignoreBlocks_) return; - - check_repeat_group_start(match); - - lastMatch_ = match.base(); - - if(!match->blocking() && match.base().parent().blocking()) { - match.pos_.back_to_parent(); - } - - //if match is not in current position & current position is blocking - //=> current position has to be advanced by one so that it is - //no longer reachable within current scope - //(can happen for repeatable, blocking parameters) - if(match.base() != pos_ && pos_->blocking()) pos_.next_sibling(); - - if(match->blocking()) { - if(match.pos_.is_alternative()) { - //discard other alternatives - match.pos_.skip_alternatives(); - } - - if(is_last_in_current_scope(match.pos_)) { - //if current param is not repeatable -> back to previous scope - if(!match->repeatable() && !match->is_group()) { - curMatched_ = false; - pos_ = std::move(match.pos_); - if(!scopes_.empty()) pos_.undo(scopes_.top()); - } - else { //stay at match position - curMatched_ = true; - pos_ = std::move(match.pos_); - } - } - else { //not last in current group - //if current param is not repeatable, go directly to next - if(!match->repeatable() && !match->is_group()) { - curMatched_ = false; - ++match.pos_; - } else { - curMatched_ = true; - } - - if(match.pos_.level() > pos_.level()) { - scopes_.push(pos_.undo_point()); - pos_ = std::move(match.pos_); - } - else if(match.pos_.level() < pos_.level()) { - return_to_level(match.pos_.level()); - } - else { - pos_ = std::move(match.pos_); - } - } - posAfterLastMatch_ = pos_; - } - else { - if(match.pos_.level() < pos_.level()) { - return_to_level(match.pos_.level()); - } - posAfterLastMatch_ = pos_; - } - repeatGroupContinues_ = repeat_group_continues(); - } - -private: - //----------------------------------------------------- - bool is_last_in_current_scope(const dfs_traverser& pos) - { - if(scopes_.empty()) return pos.is_last_in_path(); - //check if we would leave the current scope on ++ - auto p = pos; - ++p; - return p.level() < scopes_.top().level(); - } - - //----------------------------------------------------- - void check_repeat_group_start(const scoped_dfs_traverser& newMatch) - { - const auto newrg = newMatch.repeat_group(); - if(!newrg) { - repeatGroupStarted_ = false; - } - else if(lastMatch_.repeat_group() != newrg) { - repeatGroupStarted_ = true; - } - else if(!repeatGroupContinues_ || !newMatch.repeatGroupContinues_) { - repeatGroupStarted_ = true; - } - else { - //special case: repeat group is outermost group - //=> we can never really 'leave' and 'reenter' it - //but if the current scope is the first element, then we are - //conceptually at a position 'before' the group - repeatGroupStarted_ = scopes_.empty() || ( - newrg == pos_.root() && - scopes_.top().param() == &(*pos_.root()->begin()) ); - } - repeatGroupContinues_ = repeatGroupStarted_; - } - - //----------------------------------------------------- - bool repeat_group_continues() - { - if(!repeatGroupContinues_) return false; - const auto curRepGroup = pos_.repeat_group(); - if(!curRepGroup) return false; - if(curRepGroup != lastMatch_.repeat_group()) return false; - if(!posAfterLastMatch_) return false; - return true; - } - - //----------------------------------------------------- - void check_left_scope() - { - if(posAfterLastMatch_) { - if(pos_.level() < posAfterLastMatch_.level()) { - while(!scopes_.empty() && scopes_.top().level() >= pos_.level()) { - pos_.undo(scopes_.top()); - scopes_.pop(); - } - posAfterLastMatch_.invalidate(); - } - } - while(!scopes_.empty() && scopes_.top().level() > pos_.level()) { - pos_.undo(scopes_.top()); - scopes_.pop(); - } - repeatGroupContinues_ = repeat_group_continues(); - } - - //----------------------------------------------------- - void return_to_outermost_scope() - { - posAfterLastMatch_.invalidate(); - - if(scopes_.empty()) { - pos_.invalidate(); - repeatGroupContinues_ = false; - return; - } - - while(!scopes_.empty() && (!pos_ || pos_.level() >= 1)) { - pos_.undo(scopes_.top()); - scopes_.pop(); - } - while(!scopes_.empty()) scopes_.pop(); - - repeatGroupContinues_ = repeat_group_continues(); - } - - //----------------------------------------------------- - void return_to_level(int level) - { - if(pos_.level() <= level) return; - while(!scopes_.empty() && pos_.level() > level) { - pos_.undo(scopes_.top()); - scopes_.pop(); - } - }; - - dfs_traverser pos_; - dfs_traverser lastMatch_; - dfs_traverser posAfterLastMatch_; - std::stack<dfs_traverser::memento> scopes_; - bool curMatched_ = false; - bool ignoreBlocks_ = false; - bool repeatGroupStarted_ = false; - bool repeatGroupContinues_ = false; -}; - - - - -/***************************************************************************** - * - * some parameter property predicates - * - *****************************************************************************/ -struct select_all { - bool operator () (const parameter&) const noexcept { return true; } -}; - -struct select_flags { - bool operator () (const parameter& p) const noexcept { - return !p.flags().empty(); - } -}; - -struct select_values { - bool operator () (const parameter& p) const noexcept { - return p.flags().empty(); - } -}; - - - -/*************************************************************************//** - * - * @brief result of a matching operation - * - *****************************************************************************/ -class match_t { -public: - match_t() = default; - match_t(arg_string s, scoped_dfs_traverser p): - str_{std::move(s)}, pos_{std::move(p)} - {} - - const arg_string& str() const noexcept { return str_; } - const scoped_dfs_traverser& pos() const noexcept { return pos_; } - - explicit operator bool() const noexcept { return !str_.empty(); } - -private: - arg_string str_; - scoped_dfs_traverser pos_; -}; - - - -/*************************************************************************//** - * - * @brief finds the first parameter that matches a given string - * candidate parameters are traversed using a scoped DFS traverser - * - *****************************************************************************/ -template<class Predicate> -match_t -full_match(scoped_dfs_traverser pos, const arg_string& arg, - const Predicate& select) -{ - if(arg.empty()) return match_t{}; - - while(pos) { - if(pos->is_param()) { - const auto& param = pos->as_param(); - if(select(param)) { - const auto match = param.match(arg); - if(match && match.length() == arg.size()) { - return match_t{arg, std::move(pos)}; - } - } - } - ++pos; - } - return match_t{}; -} - - - -/*************************************************************************//** - * - * @brief finds the first parameter that matches any (non-empty) prefix - * of a given string; - * candidate parameters are traversed using a scoped DFS traverser - * - *****************************************************************************/ -template<class Predicate> -match_t -prefix_match(scoped_dfs_traverser pos, const arg_string& arg, - const Predicate& select) -{ - if(arg.empty()) return match_t{}; - - while(pos) { - if(pos->is_param()) { - const auto& param = pos->as_param(); - if(select(param)) { - const auto match = param.match(arg); - if(match.prefix()) { - if(match.length() == arg.size()) { - return match_t{arg, std::move(pos)}; - } - else { - return match_t{arg.substr(match.at(), match.length()), - std::move(pos)}; - } - } - } - } - ++pos; - } - return match_t{}; -} - - - -/*************************************************************************//** - * - * @brief finds the first parameter that partially matches a given string; - * candidate parameters are traversed using a scoped DFS traverser - * - *****************************************************************************/ -template<class Predicate> -match_t -partial_match(scoped_dfs_traverser pos, const arg_string& arg, - const Predicate& select) -{ - if(arg.empty()) return match_t{}; - - while(pos) { - if(pos->is_param()) { - const auto& param = pos->as_param(); - if(select(param)) { - const auto match = param.match(arg); - if(match) { - return match_t{arg.substr(match.at(), match.length()), - std::move(pos)}; - } - } - } - ++pos; - } - return match_t{}; -} - -} //namespace detail - - - - - - -/***************************************************************//** - * - * @brief default command line arguments parser - * - *******************************************************************/ -class parser -{ -public: - using dfs_traverser = group::depth_first_traverser; - using scoped_dfs_traverser = detail::scoped_dfs_traverser; - - - /*****************************************************//** - * @brief arg -> parameter mapping - *********************************************************/ - class arg_mapping { - public: - friend class parser; - - explicit - arg_mapping(arg_index idx, arg_string s, - const dfs_traverser& match) - : - index_{idx}, arg_{std::move(s)}, match_{match}, - repeat_{0}, startsRepeatGroup_{false}, - blocked_{false}, conflict_{false} - {} - - explicit - arg_mapping(arg_index idx, arg_string s) : - index_{idx}, arg_{std::move(s)}, match_{}, - repeat_{0}, startsRepeatGroup_{false}, - blocked_{false}, conflict_{false} - {} - - arg_index index() const noexcept { return index_; } - const arg_string& arg() const noexcept { return arg_; } - - const parameter* param() const noexcept { - return match_ && match_->is_param() - ? &(match_->as_param()) : nullptr; - } - - std::size_t repeat() const noexcept { return repeat_; } - - bool blocked() const noexcept { return blocked_; } - bool conflict() const noexcept { return conflict_; } - - bool bad_repeat() const noexcept { - if(!param()) return false; - return repeat_ > 0 && !param()->repeatable() - && !match_.repeat_group(); - } - - bool any_error() const noexcept { - return !match_ || blocked() || conflict() || bad_repeat(); - } - - private: - arg_index index_; - arg_string arg_; - dfs_traverser match_; - std::size_t repeat_; - bool startsRepeatGroup_; - bool blocked_; - bool conflict_; - }; - - /*****************************************************//** - * @brief references a non-matched, required parameter - *********************************************************/ - class missing_event { - public: - explicit - missing_event(const parameter* p, arg_index after): - param_{p}, aftIndex_{after} - {} - - const parameter* param() const noexcept { return param_; } - - arg_index after_index() const noexcept { return aftIndex_; } - - private: - const parameter* param_; - arg_index aftIndex_; - }; - - //----------------------------------------------------- - using missing_events = std::vector<missing_event>; - using arg_mappings = std::vector<arg_mapping>; - - -private: - struct miss_candidate { - miss_candidate(dfs_traverser p, arg_index idx, - bool firstInRepeatGroup = false): - pos{std::move(p)}, index{idx}, - startsRepeatGroup{firstInRepeatGroup} - {} - - dfs_traverser pos; - arg_index index; - bool startsRepeatGroup; - }; - using miss_candidates = std::vector<miss_candidate>; - - -public: - //--------------------------------------------------------------- - /** @brief initializes parser with a command line interface - * @param offset = argument index offset used for reports - * */ - explicit - parser(const group& root, arg_index offset = 0): - root_{&root}, pos_{root}, - index_{offset-1}, eaten_{0}, - args_{}, missCand_{}, blocked_{false} - { - for_each_potential_miss(dfs_traverser{root}, - [this](const dfs_traverser& p){ - missCand_.emplace_back(p, index_); - }); - } - - - //--------------------------------------------------------------- - /** @brief processes one command line argument */ - bool operator() (const arg_string& arg) - { - ++eaten_; - ++index_; - - if(!valid() || arg.empty()) return false; - - if(!blocked_ && try_match(arg)) return true; - - if(try_match_blocked(arg)) return false; - - //skipping of blocking & required patterns is not allowed - if(!blocked_ && !pos_.matched() && pos_->required() && pos_->blocking()) { - blocked_ = true; - return false; - } - - add_nomatch(arg); - return false; - } - - - //--------------------------------------------------------------- - /** @brief returns range of argument -> parameter mappings */ - const arg_mappings& args() const { - return args_; - } - - /** @brief returns list of missing events */ - missing_events missed() const { - missing_events misses; - misses.reserve(missCand_.size()); - for(auto i = missCand_.begin(); i != missCand_.end(); ++i) { - misses.emplace_back(&(i->pos->as_param()), i->index); - } - return misses; - } - - /** @brief returns number of processed command line arguments */ - arg_index parse_count() const noexcept { return eaten_; } - - /** @brief returns false if previously processed command line arguments - * lead to an invalid / inconsistent parsing result - */ - bool valid() const noexcept { return bool(pos_); } - - /** @brief returns false if previously processed command line arguments - * lead to an invalid / inconsistent parsing result - */ - explicit operator bool() const noexcept { return valid(); } - - -private: - //--------------------------------------------------------------- - using match_t = detail::match_t; - - - //--------------------------------------------------------------- - /** @brief try to match argument with unreachable parameter */ - bool try_match_blocked(const arg_string& arg) - { - //try to match ahead (using temporary parser) - if(pos_) { - auto ahead = *this; - if(try_match_blocked(std::move(ahead), arg)) return true; - } - - //try to match from the beginning (using temporary parser) - if(root_) { - parser all{*root_, index_+1}; - if(try_match_blocked(std::move(all), arg)) return true; - } - - return false; - } - - //--------------------------------------------------------------- - bool try_match_blocked(parser&& parse, const arg_string& arg) - { - const auto nold = int(parse.args_.size()); - - parse.pos_.ignore_blocking(true); - - if(!parse.try_match(arg)) return false; - - for(auto i = parse.args_.begin() + nold; i != parse.args_.end(); ++i) { - args_.push_back(*i); - args_.back().blocked_ = true; - } - return true; - } - - //--------------------------------------------------------------- - /** @brief try to find a parameter/pattern that matches 'arg' */ - bool try_match(const arg_string& arg) - { - //Note: flag-params will always take precedence over value-params - if(try_match_full(arg, detail::select_flags{})) return true; - if(try_match_joined_flags(arg)) return true; - if(try_match_joined_sequence(arg, detail::select_flags{})) return true; - if(try_match_full(arg, detail::select_values{})) return true; - if(try_match_joined_sequence(arg, detail::select_all{})) return true; - if(try_match_joined_params(arg)) return true; - return false; - } - - //--------------------------------------------------------------- - template<class Predicate> - bool try_match_full(const arg_string& arg, const Predicate& select) - { - auto match = detail::full_match(pos_, arg, select); - - if(!match) return false; - - add_match(match); - return true; - } - - //--------------------------------------------------------------- - template<class Predicate> - bool try_match_joined_sequence(arg_string arg, const Predicate& acceptFirst) - { - auto fstMatch = detail::prefix_match(pos_, arg, acceptFirst); - - if(!fstMatch) return false; - - if(fstMatch.str().size() == arg.size()) { - add_match(fstMatch); - return true; - } - - if(!fstMatch.pos()->blocking()) return false; - - auto pos = fstMatch.pos(); - pos.ignore_blocking(true); - const auto parent = &pos.parent(); - if(!pos->repeatable()) ++pos; - - arg.erase(0, fstMatch.str().size()); - std::vector<match_t> matches { std::move(fstMatch) }; - - while(!arg.empty() && pos && - pos->blocking() && pos->is_param() && - (&pos.parent() == parent)) - { - auto match = pos->as_param().match(arg); - - if(match.prefix()) { - matches.emplace_back(arg.substr(0,match.length()), pos); - arg.erase(0, match.length()); - if(!pos->repeatable()) ++pos; - } - else { - if(!pos->repeatable()) return false; - ++pos; - } - - } - - if(!arg.empty() || matches.empty()) return false; - - for(const auto& m : matches) add_match(m); - return true; - } - - //----------------------------------------------------- - bool try_match_joined_flags(const arg_string& arg) - { - return try_match_joined([&](const group& g) { - if(try_match_joined(g, arg, detail::select_flags{}, - g.common_flag_prefix()) ) - { - return true; - } - return false; - }); - } - - //--------------------------------------------------------------- - bool try_match_joined_params(const arg_string& arg) - { - return try_match_joined([&](const group& g) { - if(try_match_joined(g, arg, detail::select_all{}) ) { - return true; - } - return false; - }); - } - - //----------------------------------------------------- - template<class Predicate> - bool try_match_joined(const group& joinGroup, arg_string arg, - const Predicate& pred, - const arg_string& prefix = "") - { - parser parse {joinGroup}; - std::vector<match_t> matches; - - while(!arg.empty()) { - auto match = detail::prefix_match(parse.pos_, arg, pred); - - if(!match) return false; - - arg.erase(0, match.str().size()); - //make sure prefix is always present after the first match - //ensures that, e.g., flags "-a" and "-b" will be found in "-ab" - if(!arg.empty() && !prefix.empty() && arg.find(prefix) != 0 && - prefix != match.str()) - { - arg.insert(0,prefix); - } - - parse.add_match(match); - matches.push_back(std::move(match)); - } - - if(!arg.empty() || matches.empty()) return false; - - if(!parse.missCand_.empty()) return false; - for(const auto& a : parse.args_) if(a.any_error()) return false; - - //replay matches onto *this - for(const auto& m : matches) add_match(m); - return true; - } - - //----------------------------------------------------- - template<class Predicate> - bool try_match_joined(const Predicate& pred) - { - if(pos_ && pos_.parent().joinable()) { - const auto& g = pos_.parent(); - if(pred(g)) return true; - return false; - } - - auto pos = pos_; - while(pos) { - if(pos->is_group() && pos->as_group().joinable()) { - const auto& g = pos->as_group(); - if(pred(g)) return true; - pos.next_sibling(); - } - else { - ++pos; - } - } - return false; - } - - - //--------------------------------------------------------------- - void add_nomatch(const arg_string& arg) { - args_.emplace_back(index_, arg); - } - - - //--------------------------------------------------------------- - void add_match(const match_t& match) - { - const auto& pos = match.pos(); - if(!pos || !pos->is_param() || match.str().empty()) return; - - pos_.next_after_match(pos); - - arg_mapping newArg{index_, match.str(), pos.base()}; - newArg.repeat_ = occurrences_of(&pos->as_param()); - newArg.conflict_ = check_conflicts(pos.base()); - newArg.startsRepeatGroup_ = pos_.start_of_repeat_group(); - args_.push_back(std::move(newArg)); - - add_miss_candidates_after(pos); - clean_miss_candidates_for(pos.base()); - discard_alternative_miss_candidates(pos.base()); - - } - - //----------------------------------------------------- - bool check_conflicts(const dfs_traverser& match) - { - if(pos_.start_of_repeat_group()) return false; - bool conflict = false; - for(const auto& m : match.stack()) { - if(m.parent->exclusive()) { - for(auto i = args_.rbegin(); i != args_.rend(); ++i) { - if(!i->blocked()) { - for(const auto& c : i->match_.stack()) { - //sibling within same exclusive group => conflict - if(c.parent == m.parent && c.cur != m.cur) { - conflict = true; - i->conflict_ = true; - } - } - } - //check for conflicts only within current repeat cycle - if(i->startsRepeatGroup_) break; - } - } - } - return conflict; - } - - //----------------------------------------------------- - void clean_miss_candidates_for(const dfs_traverser& match) - { - auto i = std::find_if(missCand_.rbegin(), missCand_.rend(), - [&](const miss_candidate& m) { - return &(*m.pos) == &(*match); - }); - - if(i != missCand_.rend()) { - missCand_.erase(prev(i.base())); - } - } - - //----------------------------------------------------- - void discard_alternative_miss_candidates(const dfs_traverser& match) - { - if(missCand_.empty()) return; - //find out, if miss candidate is sibling of one of the same - //alternative groups that the current match is a member of - //if so, we can discard the miss - - //go through all exclusive groups of matching pattern - for(const auto& m : match.stack()) { - if(m.parent->exclusive()) { - for(auto i = int(missCand_.size())-1; i >= 0; --i) { - bool removed = false; - for(const auto& c : missCand_[i].pos.stack()) { - //sibling within same exclusive group => discard - if(c.parent == m.parent && c.cur != m.cur) { - missCand_.erase(missCand_.begin() + i); - if(missCand_.empty()) return; - removed = true; - break; - } - } - //remove miss candidates only within current repeat cycle - if(i > 0 && removed) { - if(missCand_[i-1].startsRepeatGroup) break; - } else { - if(missCand_[i].startsRepeatGroup) break; - } - } - } - } - } - - //----------------------------------------------------- - void add_miss_candidates_after(const scoped_dfs_traverser& match) - { - auto npos = match.base(); - if(npos.is_alternative()) npos.skip_alternatives(); - ++npos; - //need to add potential misses if: - //either new repeat group was started - const auto newRepGroup = match.repeat_group(); - if(newRepGroup) { - if(pos_.start_of_repeat_group()) { - for_each_potential_miss(std::move(npos), - [&,this](const dfs_traverser& pos) { - //only add candidates within repeat group - if(newRepGroup == pos.repeat_group()) { - missCand_.emplace_back(pos, index_, true); - } - }); - } - } - //... or an optional blocking param was hit - else if(match->blocking() && !match->required() && - npos.level() >= match.base().level()) - { - for_each_potential_miss(std::move(npos), - [&,this](const dfs_traverser& pos) { - //only add new candidates - if(std::find_if(missCand_.begin(), missCand_.end(), - [&](const miss_candidate& c){ - return &(*c.pos) == &(*pos); - }) == missCand_.end()) - { - missCand_.emplace_back(pos, index_); - } - }); - } - - } - - //----------------------------------------------------- - template<class Action> - static void - for_each_potential_miss(dfs_traverser pos, Action&& action) - { - const auto level = pos.level(); - while(pos && pos.level() >= level) { - if(pos->is_group() ) { - const auto& g = pos->as_group(); - if(g.all_optional() || (g.exclusive() && g.any_optional())) { - pos.next_sibling(); - } else { - ++pos; - } - } else { //param - if(pos->required()) { - action(pos); - ++pos; - } else if(pos->blocking()) { //optional + blocking - pos.next_after_siblings(); - } else { - ++pos; - } - } - } - } - - - //--------------------------------------------------------------- - std::size_t occurrences_of(const parameter* p) const - { - auto i = std::find_if(args_.rbegin(), args_.rend(), - [p](const arg_mapping& a){ return a.param() == p; }); - - if(i != args_.rend()) return i->repeat() + 1; - return 0; - } - - - //--------------------------------------------------------------- - const group* root_; - scoped_dfs_traverser pos_; - arg_index index_; - arg_index eaten_; - arg_mappings args_; - miss_candidates missCand_; - bool blocked_; -}; - - - - -/*************************************************************************//** - * - * @brief contains argument -> parameter mappings - * and missing parameters - * - *****************************************************************************/ -class parsing_result -{ -public: - using arg_mapping = parser::arg_mapping; - using arg_mappings = parser::arg_mappings; - using missing_event = parser::missing_event; - using missing_events = parser::missing_events; - using iterator = arg_mappings::const_iterator; - - //----------------------------------------------------- - /** @brief default: empty redult */ - parsing_result() = default; - - parsing_result(arg_mappings arg2param, missing_events misses): - arg2param_{std::move(arg2param)}, missing_{std::move(misses)} - {} - - //----------------------------------------------------- - /** @brief returns number of arguments that could not be mapped to - * a parameter - */ - arg_mappings::size_type - unmapped_args_count() const noexcept { - return std::count_if(arg2param_.begin(), arg2param_.end(), - [](const arg_mapping& a){ return !a.param(); }); - } - - /** @brief returns if any argument could only be matched by an - * unreachable parameter - */ - bool any_blocked() const noexcept { - return std::any_of(arg2param_.begin(), arg2param_.end(), - [](const arg_mapping& a){ return a.blocked(); }); - } - - /** @brief returns if any argument matched more than one parameter - * that were mutually exclusive */ - bool any_conflict() const noexcept { - return std::any_of(arg2param_.begin(), arg2param_.end(), - [](const arg_mapping& a){ return a.conflict(); }); - } - - /** @brief returns if any parameter matched repeatedly although - * it was not allowed to */ - bool any_bad_repeat() const noexcept { - return std::any_of(arg2param_.begin(), arg2param_.end(), - [](const arg_mapping& a){ return a.bad_repeat(); }); - } - - /** @brief returns true if any parsing error / violation of the - * command line interface definition occured */ - bool any_error() const noexcept { - return unmapped_args_count() > 0 || !missing().empty() || - any_blocked() || any_conflict() || any_bad_repeat(); - } - - /** @brief returns true if no parsing error / violation of the - * command line interface definition occured */ - explicit operator bool() const noexcept { return !any_error(); } - - /** @brief access to range of missing parameter match events */ - const missing_events& missing() const noexcept { return missing_; } - - /** @brief returns non-mutating iterator to position of - * first argument -> parameter mapping */ - iterator begin() const noexcept { return arg2param_.begin(); } - /** @brief returns non-mutating iterator to position one past the - * last argument -> parameter mapping */ - iterator end() const noexcept { return arg2param_.end(); } - -private: - //----------------------------------------------------- - arg_mappings arg2param_; - missing_events missing_; -}; - - - - -namespace detail { -namespace { - -/*************************************************************************//** - * - * @brief correct some common problems - * does not - and MUST NOT - change the number of arguments - * (no insertion, no deletion) - * - *****************************************************************************/ -void sanitize_args(arg_list& args) -{ - //e.g. {"-o12", ".34"} -> {"-o", "12.34"} - - if(args.empty()) return; - - for(auto i = begin(args)+1; i != end(args); ++i) { - if(i != begin(args) && i->size() > 1 && - i->find('.') == 0 && std::isdigit((*i)[1]) ) - { - //find trailing digits in previous arg - using std::prev; - auto& prv = *prev(i); - auto fstDigit = std::find_if_not(prv.rbegin(), prv.rend(), - [](arg_string::value_type c){ - return std::isdigit(c); - }).base(); - - //handle leading sign - if(fstDigit > prv.begin() && - (*prev(fstDigit) == '+' || *prev(fstDigit) == '-')) - { - --fstDigit; - } - - //prepend digits from previous arg - i->insert(begin(*i), fstDigit, end(prv)); - - //erase digits in previous arg - prv.erase(fstDigit, end(prv)); - } - } -} - - - -/*************************************************************************//** - * - * @brief executes actions based on a parsing result - * - *****************************************************************************/ -void execute_actions(const parsing_result& res) -{ - for(const auto& m : res) { - if(m.param()) { - const auto& param = *(m.param()); - - if(m.repeat() > 0) param.notify_repeated(m.index()); - if(m.blocked()) param.notify_blocked(m.index()); - if(m.conflict()) param.notify_conflict(m.index()); - //main action - if(!m.any_error()) param.execute_actions(m.arg()); - } - } - - for(auto m : res.missing()) { - if(m.param()) m.param()->notify_missing(m.after_index()); - } -} - - - -/*************************************************************************//** - * - * @brief parses input args - * - *****************************************************************************/ -static parsing_result -parse_args(const arg_list& args, const group& cli, - arg_index offset = 0) -{ - //parse args and store unrecognized arg indices - parser parse{cli, offset}; - for(const auto& arg : args) { - parse(arg); - if(!parse.valid()) break; - } - - return parsing_result{parse.args(), parse.missed()}; -} - -/*************************************************************************//** - * - * @brief parses input args & executes actions - * - *****************************************************************************/ -static parsing_result -parse_and_execute(const arg_list& args, const group& cli, - arg_index offset = 0) -{ - auto result = parse_args(args, cli, offset); - - execute_actions(result); - - return result; -} - -} //anonymous namespace -} // namespace detail - - - - -/*************************************************************************//** - * - * @brief parses vector of arg strings and executes actions - * - *****************************************************************************/ -inline parsing_result -parse(arg_list args, const group& cli) -{ - detail::sanitize_args(args); - return detail::parse_and_execute(args, cli); -} - - -/*************************************************************************//** - * - * @brief parses initializer_list of C-style arg strings and executes actions - * - *****************************************************************************/ -inline parsing_result -parse(std::initializer_list<const char*> arglist, const group& cli) -{ - arg_list args; - args.reserve(arglist.size()); - for(auto a : arglist) { - if(std::strlen(a) > 0) args.push_back(a); - } - - return parse(std::move(args), cli); -} - - -/*************************************************************************//** - * - * @brief parses range of arg strings and executes actions - * - *****************************************************************************/ -template<class InputIterator> -inline parsing_result -parse(InputIterator first, InputIterator last, const group& cli) -{ - return parse(arg_list(first,last), cli); -} - - -/*************************************************************************//** - * - * @brief parses the standard array of command line arguments; omits argv[0] - * - *****************************************************************************/ -inline parsing_result -parse(const int argc, char* argv[], const group& cli, arg_index offset = 1) -{ - arg_list args; - if(offset < argc) args.assign(argv+offset, argv+argc); - detail::sanitize_args(args); - return detail::parse_and_execute(args, cli, offset); -} - - - - - - -/*************************************************************************//** - * - * @brief filter predicate for parameters and groups; - * Can be used to limit documentation generation to parameter subsets. - * - *****************************************************************************/ -class param_filter -{ -public: - /** @brief only allow parameters with given prefix */ - param_filter& prefix(const arg_string& p) noexcept { - prefix_ = p; return *this; - } - /** @brief only allow parameters with given prefix */ - param_filter& prefix(arg_string&& p) noexcept { - prefix_ = std::move(p); return *this; - } - const arg_string& prefix() const noexcept { return prefix_; } - - /** @brief only allow parameters with given requirement status */ - param_filter& required(tri t) noexcept { required_ = t; return *this; } - tri required() const noexcept { return required_; } - - /** @brief only allow parameters with given blocking status */ - param_filter& blocking(tri t) noexcept { blocking_ = t; return *this; } - tri blocking() const noexcept { return blocking_; } - - /** @brief only allow parameters with given repeatable status */ - param_filter& repeatable(tri t) noexcept { repeatable_ = t; return *this; } - tri repeatable() const noexcept { return repeatable_; } - - /** @brief only allow parameters with given docstring status */ - param_filter& has_doc(tri t) noexcept { hasDoc_ = t; return *this; } - tri has_doc() const noexcept { return hasDoc_; } - - - /** @brief returns true, if parameter satisfies all filters */ - bool operator() (const parameter& p) const noexcept { - if(!prefix_.empty()) { - if(!std::any_of(p.flags().begin(), p.flags().end(), - [&](const arg_string& flag){ - return str::has_prefix(flag, prefix_); - })) return false; - } - if(required() != p.required()) return false; - if(blocking() != p.blocking()) return false; - if(repeatable() != p.repeatable()) return false; - if(has_doc() != !p.doc().empty()) return false; - return true; - } - -private: - arg_string prefix_; - tri required_ = tri::either; - tri blocking_ = tri::either; - tri repeatable_ = tri::either; - tri exclusive_ = tri::either; - tri hasDoc_ = tri::yes; -}; - - - - - - -/*************************************************************************//** - * - * @brief documentation formatting options - * - *****************************************************************************/ -class doc_formatting -{ -public: - using string = doc_string; - - /** @brief determines column where documentation printing starts */ - doc_formatting& start_column(int col) { startCol_ = col; return *this; } - int start_column() const noexcept { return startCol_; } - - /** @brief determines column where docstrings start */ - doc_formatting& doc_column(int col) { docCol_ = col; return *this; } - int doc_column() const noexcept { return docCol_; } - - /** @brief determines indent of documentation lines - * for children of a documented group */ - doc_formatting& indent_size(int indent) { indentSize_ = indent; return *this; } - int indent_size() const noexcept { return indentSize_; } - - /** @brief determines string to be used - * if a parameter has no flags and no label */ - doc_formatting& empty_label(const string& label) { - emptyLabel_ = label; - return *this; - } - const string& empty_label() const noexcept { return emptyLabel_; } - - /** @brief determines string for separating parameters */ - doc_formatting& param_separator(const string& sep) { - paramSep_ = sep; - return *this; - } - const string& param_separator() const noexcept { return paramSep_; } - - /** @brief determines string for separating groups (in usage lines) */ - doc_formatting& group_separator(const string& sep) { - groupSep_ = sep; - return *this; - } - const string& group_separator() const noexcept { return groupSep_; } - - /** @brief determines string for separating alternative parameters */ - doc_formatting& alternative_param_separator(const string& sep) { - altParamSep_ = sep; - return *this; - } - const string& alternative_param_separator() const noexcept { return altParamSep_; } - - /** @brief determines string for separating alternative groups */ - doc_formatting& alternative_group_separator(const string& sep) { - altGroupSep_ = sep; - return *this; - } - const string& alternative_group_separator() const noexcept { return altGroupSep_; } - - /** @brief determines string for separating flags of the same parameter */ - doc_formatting& flag_separator(const string& sep) { - flagSep_ = sep; - return *this; - } - const string& flag_separator() const noexcept { return flagSep_; } - - /** @brief determnines strings surrounding parameter labels */ - doc_formatting& - surround_labels(const string& prefix, const string& postfix) { - labelPre_ = prefix; - labelPst_ = postfix; - return *this; - } - const string& label_prefix() const noexcept { return labelPre_; } - const string& label_postfix() const noexcept { return labelPst_; } - - /** @brief determnines strings surrounding optional parameters/groups */ - doc_formatting& - surround_optional(const string& prefix, const string& postfix) { - optionPre_ = prefix; - optionPst_ = postfix; - return *this; - } - const string& optional_prefix() const noexcept { return optionPre_; } - const string& optional_postfix() const noexcept { return optionPst_; } - - /** @brief determnines strings surrounding repeatable parameters/groups */ - doc_formatting& - surround_repeat(const string& prefix, const string& postfix) { - repeatPre_ = prefix; - repeatPst_ = postfix; - return *this; - } - const string& repeat_prefix() const noexcept { return repeatPre_; } - const string& repeat_postfix() const noexcept { return repeatPst_; } - - /** @brief determnines strings surrounding exclusive groups */ - doc_formatting& - surround_alternatives(const string& prefix, const string& postfix) { - alternPre_ = prefix; - alternPst_ = postfix; - return *this; - } - const string& alternatives_prefix() const noexcept { return alternPre_; } - const string& alternatives_postfix() const noexcept { return alternPst_; } - - /** @brief determnines strings surrounding alternative flags */ - doc_formatting& - surround_alternative_flags(const string& prefix, const string& postfix) { - alternFlagPre_ = prefix; - alternFlagPst_ = postfix; - return *this; - } - const string& alternative_flags_prefix() const noexcept { return alternFlagPre_; } - const string& alternative_flags_postfix() const noexcept { return alternFlagPst_; } - - /** @brief determnines strings surrounding non-exclusive groups */ - doc_formatting& - surround_group(const string& prefix, const string& postfix) { - groupPre_ = prefix; - groupPst_ = postfix; - return *this; - } - const string& group_prefix() const noexcept { return groupPre_; } - const string& group_postfix() const noexcept { return groupPst_; } - - /** @brief determnines strings surrounding joinable groups */ - doc_formatting& - surround_joinable(const string& prefix, const string& postfix) { - joinablePre_ = prefix; - joinablePst_ = postfix; - return *this; - } - const string& joinable_prefix() const noexcept { return joinablePre_; } - const string& joinable_postfix() const noexcept { return joinablePst_; } - - /** @brief determines maximum number of flags per parameter to be printed - * in detailed parameter documentation lines */ - doc_formatting& max_flags_per_param_in_doc(int max) { - maxAltInDocs_ = max > 0 ? max : 0; - return *this; - } - int max_flags_per_param_in_doc() const noexcept { return maxAltInDocs_; } - - /** @brief determines maximum number of flags per parameter to be printed - * in usage lines */ - doc_formatting& max_flags_per_param_in_usage(int max) { - maxAltInUsage_ = max > 0 ? max : 0; - return *this; - } - int max_flags_per_param_in_usage() const noexcept { return maxAltInUsage_; } - - /** @brief determines number of empty rows after one single-line - * documentation entry */ - doc_formatting& line_spacing(int lines) { - lineSpc_ = lines > 0 ? lines : 0; - return *this; - } - int line_spacing() const noexcept { return lineSpc_; } - - /** @brief determines number of empty rows before and after a paragraph; - * a paragraph is defined by a documented group or if - * a parameter documentation entry used more than one line */ - doc_formatting& paragraph_spacing(int lines) { - paragraphSpc_ = lines > 0 ? lines : 0; - return *this; - } - int paragraph_spacing() const noexcept { return paragraphSpc_; } - - /** @brief determines if alternative flags with a common prefix should - * be printed in a merged fashion */ - doc_formatting& merge_alternative_flags_with_common_prefix(bool yes = true) { - mergeAltCommonPfx_ = yes; - return *this; - } - bool merge_alternative_flags_with_common_prefix() const noexcept { - return mergeAltCommonPfx_; - } - - /** @brief determines if joinable flags with a common prefix should - * be printed in a merged fashion */ - doc_formatting& merge_joinable_with_common_prefix(bool yes = true) { - mergeJoinableCommonPfx_ = yes; - return *this; - } - bool merge_joinable_with_common_prefix() const noexcept { - return mergeJoinableCommonPfx_; - } - - /** @brief determines if children of exclusive groups should be printed - * on individual lines if the exceed 'alternatives_min_split_size' - */ - doc_formatting& split_alternatives(bool yes = true) { - splitTopAlt_ = yes; - return *this; - } - bool split_alternatives() const noexcept { - return splitTopAlt_; - } - - /** @brief determines how many children exclusive groups can have before - * their children are printed on individual usage lines */ - doc_formatting& alternatives_min_split_size(int size) { - groupSplitSize_ = size > 0 ? size : 0; - return *this; - } - int alternatives_min_split_size() const noexcept { return groupSplitSize_; } - -private: - string paramSep_ = string(" "); - string groupSep_ = string(" "); - string altParamSep_ = string("|"); - string altGroupSep_ = string(" | "); - string flagSep_ = string(", "); - string labelPre_ = string("<"); - string labelPst_ = string(">"); - string optionPre_ = string("["); - string optionPst_ = string("]"); - string repeatPre_ = string(""); - string repeatPst_ = string("..."); - string groupPre_ = string("("); - string groupPst_ = string(")"); - string alternPre_ = string("("); - string alternPst_ = string(")"); - string alternFlagPre_ = string(""); - string alternFlagPst_ = string(""); - string joinablePre_ = string("("); - string joinablePst_ = string(")"); - string emptyLabel_ = string(""); - int startCol_ = 8; - int docCol_ = 20; - int indentSize_ = 4; - int maxAltInUsage_ = 1; - int maxAltInDocs_ = 32; - int lineSpc_ = 0; - int paragraphSpc_ = 1; - int groupSplitSize_ = 3; - bool splitTopAlt_ = true; - bool mergeAltCommonPfx_ = false; - bool mergeJoinableCommonPfx_ = true; -}; - - - - -/*************************************************************************//** - * - * @brief generates usage lines - * - * @details lazily evaluated - * - *****************************************************************************/ -class usage_lines -{ -public: - using string = doc_string; - - usage_lines(const group& params, string prefix = "", - const doc_formatting& fmt = doc_formatting{}) - : - params_(params), fmt_(fmt), prefix_(std::move(prefix)) - { - if(!prefix_.empty()) prefix_ += ' '; - if(fmt_.start_column() > 0) prefix_.insert(0, fmt.start_column(), ' '); - } - - usage_lines(const group& params, const doc_formatting& fmt): - usage_lines(params, "", fmt) - {} - - usage_lines& ommit_outermost_group_surrounders(bool yes) { - ommitOutermostSurrounders_ = yes; - return *this; - } - bool ommit_outermost_group_surrounders() const { - return ommitOutermostSurrounders_; - } - - template<class OStream> - inline friend OStream& operator << (OStream& os, const usage_lines& p) { - p.print_usage(os); - return os; - } - - string str() const { - std::ostringstream os; os << *this; return os.str(); - } - - -private: - const group& params_; - doc_formatting fmt_; - string prefix_; - bool ommitOutermostSurrounders_ = false; - - - //----------------------------------------------------- - struct context { - group::depth_first_traverser pos; - std::stack<string> separators; - std::stack<string> postfixes; - int level = 0; - const group* outermost = nullptr; - bool linestart = false; - bool useOutermost = true; - int line = 0; - - bool is_singleton() const noexcept { - return linestart && pos.is_last_in_path(); - } - bool is_alternative() const noexcept { - return pos.parent().exclusive(); - } - }; - - - /***************************************************************//** - * - * @brief writes usage text for command line parameters - * - *******************************************************************/ - template<class OStream> - void print_usage(OStream& os) const - { - context cur; - cur.pos = params_.begin_dfs(); - cur.linestart = true; - cur.level = cur.pos.level(); - cur.outermost = ¶ms_; - - print_usage(os, cur, prefix_); - } - - - /***************************************************************//** - * - * @brief writes usage text for command line parameters - * - * @param prefix all that goes in front of current things to print - * - *******************************************************************/ - template<class OStream> - void print_usage(OStream& os, context cur, string prefix) const - { - if(!cur.pos) return; - - std::ostringstream buf; - if(cur.linestart) buf << prefix; - const auto initPos = buf.tellp(); - - cur.level = cur.pos.level(); - - if(cur.useOutermost) { - //we cannot start outside of the outermost group - //so we have to treat it separately - start_group(buf, cur.pos.parent(), cur); - if(!cur.pos) { - os << buf.str(); - return; - } - } - else { - //don't visit siblings of starter node - cur.pos.skip_siblings(); - } - check_end_group(buf, cur); - - do { - if(buf.tellp() > initPos) cur.linestart = false; - if(!cur.linestart && !cur.pos.is_first_in_group()) { - buf << cur.separators.top(); - } - if(cur.pos->is_group()) { - start_group(buf, cur.pos->as_group(), cur); - if(!cur.pos) { - os << buf.str(); - return; - } - } - else { - buf << param_label(cur.pos->as_param(), cur); - ++cur.pos; - } - check_end_group(buf, cur); - } while(cur.pos); - - os << buf.str(); - } - - - /***************************************************************//** - * - * @brief handles pattern group surrounders and separators - * and alternative splitting - * - *******************************************************************/ - void start_group(std::ostringstream& os, - const group& group, context& cur) const - { - //does cur.pos already point to a member or to group itself? - //needed for special treatment of outermost group - const bool alreadyInside = &(cur.pos.parent()) == &group; - - auto lbl = joined_label(group, cur); - if(!lbl.empty()) { - os << lbl; - cur.linestart = false; - //skip over entire group as its label has already been created - if(alreadyInside) { - cur.pos.next_after_siblings(); - } else { - cur.pos.next_sibling(); - } - } - else { - const bool splitAlternatives = group.exclusive() && - fmt_.split_alternatives() && - std::any_of(group.begin(), group.end(), - [this](const pattern& p) { - return int(p.param_count()) >= fmt_.alternatives_min_split_size(); - }); - - if(splitAlternatives) { - cur.postfixes.push(""); - cur.separators.push(""); - //recursively print alternative paths in decision-DAG - //enter group? - if(!alreadyInside) ++cur.pos; - cur.linestart = true; - cur.useOutermost = false; - auto pfx = os.str(); - os.str(""); - //print paths in DAG starting at each group member - for(std::size_t i = 0; i < group.size(); ++i) { - std::stringstream buf; - cur.outermost = cur.pos->is_group() ? &(cur.pos->as_group()) : nullptr; - print_usage(buf, cur, pfx); - if(buf.tellp() > int(pfx.size())) { - os << buf.str(); - if(i < group.size()-1) { - if(cur.line > 0) { - os << string(fmt_.line_spacing(), '\n'); - } - ++cur.line; - os << '\n'; - } - } - cur.pos.next_sibling(); //do not descend into memebers - } - cur.pos.invalidate(); //signal end-of-path - return; - } - else { - //pre & postfixes, separators - auto surround = group_surrounders(group, cur); - os << surround.first; - cur.postfixes.push(std::move(surround.second)); - cur.separators.push(group_separator(group, fmt_)); - //descend into group? - if(!alreadyInside) ++cur.pos; - } - } - cur.level = cur.pos.level(); - } - - - /***************************************************************//** - * - *******************************************************************/ - void check_end_group(std::ostringstream& os, context& cur) const - { - for(; cur.level > cur.pos.level(); --cur.level) { - os << cur.postfixes.top(); - cur.postfixes.pop(); - cur.separators.pop(); - } - cur.level = cur.pos.level(); - } - - - /***************************************************************//** - * - * @brief makes usage label for one command line parameter - * - *******************************************************************/ - string param_label(const parameter& p, const context& cur) const - { - const auto& parent = cur.pos.parent(); - - const bool startsOptionalSequence = - parent.size() > 1 && p.blocking() && cur.pos.is_first_in_group(); - - const bool outermost = - ommitOutermostSurrounders_ && cur.outermost == &parent; - - const bool showopt = !cur.is_alternative() && !p.required() - && !startsOptionalSequence && !outermost; - - const bool showrep = p.repeatable() && !outermost; - - string lbl; - - if(showrep) lbl += fmt_.repeat_prefix(); - if(showopt) lbl += fmt_.optional_prefix(); - - const auto& flags = p.flags(); - if(!flags.empty()) { - const int n = std::min(fmt_.max_flags_per_param_in_usage(), - int(flags.size())); - - const bool surrAlt = n > 1 && !showopt && !cur.is_singleton(); - - if(surrAlt) lbl += fmt_.alternative_flags_prefix(); - bool sep = false; - for(int i = 0; i < n; ++i) { - if(sep) { - if(cur.is_singleton()) - lbl += fmt_.alternative_group_separator(); - else - lbl += fmt_.flag_separator(); - } - lbl += flags[i]; - sep = true; - } - if(surrAlt) lbl += fmt_.alternative_flags_postfix(); - } - else { - if(!p.label().empty()) { - lbl += fmt_.label_prefix() - + p.label() - + fmt_.label_postfix(); - } else if(!fmt_.empty_label().empty()) { - lbl += fmt_.label_prefix() - + fmt_.empty_label() - + fmt_.label_postfix(); - } else { - return ""; - } - } - - if(showopt) lbl += fmt_.optional_postfix(); - if(showrep) lbl += fmt_.repeat_postfix(); - - return lbl; - } - - - /***************************************************************//** - * - * @brief prints flags in one group in a merged fashion - * - *******************************************************************/ - string joined_label(const group& params, const context& cur) const - { - if(!fmt_.merge_alternative_flags_with_common_prefix() && - !fmt_.merge_joinable_with_common_prefix()) return ""; - - const bool flagsonly = std::all_of(params.begin(), params.end(), - [](const pattern& p){ - return p.is_param() && !p.as_param().flags().empty(); - }); - - if(!flagsonly) return ""; - - const bool showOpt = params.all_optional() && - !(ommitOutermostSurrounders_ && cur.outermost == ¶ms); - - auto pfx = params.common_flag_prefix(); - if(pfx.empty()) return ""; - - const auto n = pfx.size(); - if(params.exclusive() && - fmt_.merge_alternative_flags_with_common_prefix()) - { - string lbl; - if(showOpt) lbl += fmt_.optional_prefix(); - lbl += pfx + fmt_.alternatives_prefix(); - bool first = true; - for(const auto& p : params) { - if(p.is_param()) { - if(first) - first = false; - else - lbl += fmt_.alternative_param_separator(); - lbl += p.as_param().flags().front().substr(n); - } - } - lbl += fmt_.alternatives_postfix(); - if(showOpt) lbl += fmt_.optional_postfix(); - return lbl; - } - //no alternatives, but joinable flags - else if(params.joinable() && - fmt_.merge_joinable_with_common_prefix()) - { - const bool allSingleChar = std::all_of(params.begin(), params.end(), - [&](const pattern& p){ - return p.is_param() && - p.as_param().flags().front().substr(n).size() == 1; - }); - - if(allSingleChar) { - string lbl; - if(showOpt) lbl += fmt_.optional_prefix(); - lbl += pfx; - for(const auto& p : params) { - if(p.is_param()) - lbl += p.as_param().flags().front().substr(n); - } - if(showOpt) lbl += fmt_.optional_postfix(); - return lbl; - } - } - - return ""; - } - - - /***************************************************************//** - * - * @return symbols with which to surround a group - * - *******************************************************************/ - std::pair<string,string> - group_surrounders(const group& group, const context& cur) const - { - string prefix; - string postfix; - - const bool isOutermost = &group == cur.outermost; - if(isOutermost && ommitOutermostSurrounders_) - return {string{}, string{}}; - - if(group.exclusive()) { - if(group.all_optional()) { - prefix = fmt_.optional_prefix(); - postfix = fmt_.optional_postfix(); - if(group.all_flagless()) { - prefix += fmt_.label_prefix(); - postfix = fmt_.label_prefix() + postfix; - } - } else if(group.all_flagless()) { - prefix = fmt_.label_prefix(); - postfix = fmt_.label_postfix(); - } else if(!cur.is_singleton() || !isOutermost) { - prefix = fmt_.alternatives_prefix(); - postfix = fmt_.alternatives_postfix(); - } - } - else if(group.size() > 1 && - group.front().blocking() && !group.front().required()) - { - prefix = fmt_.optional_prefix(); - postfix = fmt_.optional_postfix(); - } - else if(group.size() > 1 && cur.is_alternative() && - &group != cur.outermost) - { - prefix = fmt_.group_prefix(); - postfix = fmt_.group_postfix(); - } - else if(!group.exclusive() && - group.joinable() && !cur.linestart) - { - prefix = fmt_.joinable_prefix(); - postfix = fmt_.joinable_postfix(); - } - - if(group.repeatable()) { - if(prefix.empty()) prefix = fmt_.group_prefix(); - prefix = fmt_.repeat_prefix() + prefix; - if(postfix.empty()) postfix = fmt_.group_postfix(); - postfix += fmt_.repeat_postfix(); - } - - return {std::move(prefix), std::move(postfix)}; - } - - - /***************************************************************//** - * - * @return symbol that separates members of a group - * - *******************************************************************/ - static string - group_separator(const group& group, const doc_formatting& fmt) - { - const bool only1ParamPerMember = std::all_of(group.begin(), group.end(), - [](const pattern& p) { return p.param_count() < 2; }); - - if(only1ParamPerMember) { - if(group.exclusive()) { - return fmt.alternative_param_separator(); - } else { - return fmt.param_separator(); - } - } - else { //there is at least one large group inside - if(group.exclusive()) { - return fmt.alternative_group_separator(); - } else { - return fmt.group_separator(); - } - } - } -}; - - - - -/*************************************************************************//** - * - * @brief generates parameter and group documentation from docstrings - * - * @details lazily evaluated - * - *****************************************************************************/ -class documentation -{ -public: - using string = doc_string; - - documentation(const group& cli, - const doc_formatting& fmt = doc_formatting{}, - const param_filter& filter = param_filter{}) - : - cli_(cli), fmt_{fmt}, usgFmt_{fmt}, filter_{filter} - { - //necessary, because we re-use "usage_lines" to generate - //labels for documented groups - usgFmt_.max_flags_per_param_in_usage( - usgFmt_.max_flags_per_param_in_doc()); - } - - documentation(const group& params, - const param_filter& filter, - const doc_formatting& fmt = doc_formatting{}) - : - documentation(params, fmt, filter) - {} - - template<class OStream> - inline friend OStream& operator << (OStream& os, const documentation& p) { - printed prn = printed::nothing; - p.print_doc(os, p.cli_, prn); - return os; - } - - string str() const { - std::ostringstream os; os << *this; return os.str(); - } - - -private: - using dfs_traverser = group::depth_first_traverser; - enum class printed { nothing, line, paragraph }; - - const group& cli_; - doc_formatting fmt_; - doc_formatting usgFmt_; - param_filter filter_; - - - /***************************************************************//** - * - * @brief writes full documentation text for command line parameters - * - *******************************************************************/ - template<class OStream> - void print_doc(OStream& os, const group& params, - printed& sofar, - int indentLvl = 0) const - { - if(params.empty()) return; - - //if group itself doesn't have docstring - if(params.doc().empty()) { - for(const auto& p : params) { - print_doc(os, p, sofar, indentLvl); - } - } - else { //group itself does have docstring - bool anyDocInside = std::any_of(params.begin(), params.end(), - [](const pattern& p){ return !p.doc().empty(); }); - - if(anyDocInside) { //group docstring as title, then child entries - if(sofar != printed::nothing) { - os << string(fmt_.paragraph_spacing() + 1, '\n'); - } - auto indent = string(fmt_.start_column(), ' '); - if(indentLvl > 0) indent += string(fmt_.indent_size() * indentLvl, ' '); - os << indent << params.doc() << '\n'; - sofar = printed::nothing; - for(const auto& p : params) { - print_doc(os, p, sofar, indentLvl + 1); - } - sofar = printed::paragraph; - } - else { //group label first then group docstring - auto lbl = usage_lines(params, usgFmt_) - .ommit_outermost_group_surrounders(true).str(); - - str::trim(lbl); - print_entry(os, lbl, params.doc(), fmt_, sofar, indentLvl); - } - } - } - - - /***************************************************************//** - * - * @brief writes documentation text for one group or parameter - * - *******************************************************************/ - template<class OStream> - void print_doc(OStream& os, const pattern& ptrn, - printed& sofar, int indentLvl) const - { - if(ptrn.is_group()) { - print_doc(os, ptrn.as_group(), sofar, indentLvl); - } - else { - const auto& p = ptrn.as_param(); - if(!filter_(p)) return; - print_entry(os, param_label(p, fmt_), p.doc(), fmt_, sofar, indentLvl); - } - } - - - /*********************************************************************//** - * - * @brief prints one entry = label + docstring - * - ************************************************************************/ - template<class OStream> - static void - print_entry(OStream& os, - const string& label, const string& docstr, - const doc_formatting& fmt, printed& sofar, int indentLvl) - { - if(label.empty()) return; - - auto indent = string(fmt.start_column(), ' '); - if(indentLvl > 0) indent += string(fmt.indent_size() * indentLvl, ' '); - - const auto len = int(indent.size() + label.size()); - const bool oneline = len < fmt.doc_column(); - - if(oneline) { - if(sofar == printed::line) - os << string(fmt.line_spacing() + 1, '\n'); - else if(sofar == printed::paragraph) - os << string(fmt.paragraph_spacing() + 1, '\n'); - } - else if(sofar != printed::nothing) { - os << string(fmt.paragraph_spacing() + 1, '\n'); - } - - sofar = oneline ? printed::line : printed::paragraph; - - os << indent << label; - - if(!docstr.empty()) { - if(oneline) { - os << string(fmt.doc_column() - len, ' '); - } else { - os << '\n' << string(fmt.doc_column(), ' '); - } - os << docstr; - } - } - - - /*********************************************************************//** - * - * @brief makes label for one parameter - * - ************************************************************************/ - static doc_string - param_label(const parameter& param, const doc_formatting& fmt) - { - doc_string lbl; - - if(param.repeatable()) lbl += fmt.repeat_prefix(); - - const auto& flags = param.flags(); - if(!flags.empty()) { - lbl += flags[0]; - const int n = std::min(fmt.max_flags_per_param_in_doc(), - int(flags.size())); - for(int i = 1; i < n; ++i) { - lbl += fmt.flag_separator() + flags[i]; - } - } - else if(!param.label().empty() || !fmt.empty_label().empty()) { - lbl += fmt.label_prefix(); - if(!param.label().empty()) { - lbl += param.label(); - } else { - lbl += fmt.empty_label(); - } - lbl += fmt.label_postfix(); - } - - if(param.repeatable()) lbl += fmt.repeat_postfix(); - - return lbl; - } - -}; - - - - -/*************************************************************************//** - * - * @brief stores strings for man page sections - * - *****************************************************************************/ -class man_page -{ -public: - //--------------------------------------------------------------- - using string = doc_string; - - //--------------------------------------------------------------- - /** @brief man page section */ - class section { - public: - using string = doc_string; - - section(string stitle, string scontent): - title_{std::move(stitle)}, content_{std::move(scontent)} - {} - - const string& title() const noexcept { return title_; } - const string& content() const noexcept { return content_; } - - private: - string title_; - string content_; - }; - -private: - using section_store = std::vector<section>; - -public: - //--------------------------------------------------------------- - using value_type = section; - using const_iterator = section_store::const_iterator; - using size_type = section_store::size_type; - - - //--------------------------------------------------------------- - man_page& - append_section(string title, string content) - { - sections_.emplace_back(std::move(title), std::move(content)); - return *this; - } - //----------------------------------------------------- - man_page& - prepend_section(string title, string content) - { - sections_.emplace(sections_.begin(), - std::move(title), std::move(content)); - return *this; - } - - - //--------------------------------------------------------------- - const section& operator [] (size_type index) const noexcept { - return sections_[index]; - } - - //--------------------------------------------------------------- - size_type size() const noexcept { return sections_.size(); } - - bool empty() const noexcept { return sections_.empty(); } - - - //--------------------------------------------------------------- - const_iterator begin() const noexcept { return sections_.begin(); } - const_iterator end() const noexcept { return sections_.end(); } - - - //--------------------------------------------------------------- - man_page& program_name(const string& n) { - progName_ = n; - return *this; - } - man_page& program_name(string&& n) { - progName_ = std::move(n); - return *this; - } - const string& program_name() const noexcept { - return progName_; - } - - - //--------------------------------------------------------------- - man_page& section_row_spacing(int rows) { - sectionSpc_ = rows > 0 ? rows : 0; - return *this; - } - int section_row_spacing() const noexcept { return sectionSpc_; } - - -private: - int sectionSpc_ = 1; - section_store sections_; - string progName_; -}; - - - -/*************************************************************************//** - * - * @brief generates man sections from command line parameters - * with sections "synopsis" and "options" - * - *****************************************************************************/ -inline man_page -make_man_page(const group& params, - doc_string progname = "", - const doc_formatting& fmt = doc_formatting{}) -{ - man_page man; - man.append_section("SYNOPSIS", usage_lines(params,progname,fmt).str()); - man.append_section("OPTIONS", documentation(params,fmt).str()); - return man; -} - - - -/*************************************************************************//** - * - * @brief generates man page based on command line parameters - * - *****************************************************************************/ -template<class OStream> -OStream& -operator << (OStream& os, const man_page& man) -{ - bool first = true; - const auto secSpc = doc_string(man.section_row_spacing() + 1, '\n'); - for(const auto& section : man) { - if(!section.content().empty()) { - if(first) first = false; else os << secSpc; - if(!section.title().empty()) os << section.title() << '\n'; - os << section.content(); - } - } - os << '\n'; - return os; -} - - - - - -/*************************************************************************//** - * - * @brief printing methods for debugging command line interfaces - * - *****************************************************************************/ -namespace debug { - - -/*************************************************************************//** - * - * @brief prints first flag or value label of a parameter - * - *****************************************************************************/ -inline doc_string doc_label(const parameter& p) -{ - if(!p.flags().empty()) return p.flags().front(); - if(!p.label().empty()) return p.label(); - return doc_string{"<?>"}; -} - -inline doc_string doc_label(const group&) -{ - return "<group>"; -} - -inline doc_string doc_label(const pattern& p) -{ - return p.is_group() ? doc_label(p.as_group()) : doc_label(p.as_param()); -} - - -/*************************************************************************//** - * - * @brief prints parsing result - * - *****************************************************************************/ -template<class OStream> -void print(OStream& os, const parsing_result& result) -{ - for(const auto& m : result) { - os << "#" << m.index() << " " << m.arg() << " -> "; - auto p = m.param(); - if(p) { - os << doc_label(*p) << " \t"; - if(m.repeat() > 0) { - os << (m.bad_repeat() ? "[bad repeat " : "[repeat ") - << m.repeat() << "]"; - } - if(m.blocked()) os << " [blocked]"; - if(m.conflict()) os << " [conflict]"; - os << '\n'; - } - else { - os << " [unmapped]\n"; - } - } - - for(const auto& m : result.missing()) { - auto p = m.param(); - if(p) { - os << doc_label(*p) << " \t"; - os << " [missing after " << m.after_index() << "]\n"; - } - } -} - - -/*************************************************************************//** - * - * @brief prints parameter label and some properties - * - *****************************************************************************/ -template<class OStream> -void print(OStream& os, const parameter& p) -{ - if(p.blocking()) os << '!'; - if(!p.required()) os << '['; - os << doc_label(p); - if(p.repeatable()) os << "..."; - if(!p.required()) os << "]"; -} - - -//------------------------------------------------------------------- -template<class OStream> -void print(OStream& os, const group& g, int level = 0); - - -/*************************************************************************//** - * - * @brief prints group or parameter; uses indentation - * - *****************************************************************************/ -template<class OStream> -void print(OStream& os, const pattern& param, int level = 0) -{ - if(param.is_group()) { - print(os, param.as_group(), level); - } - else { - os << doc_string(4*level, ' '); - print(os, param.as_param()); - } -} - - -/*************************************************************************//** - * - * @brief prints group and its contents; uses indentation - * - *****************************************************************************/ -template<class OStream> -void print(OStream& os, const group& g, int level) -{ - auto indent = doc_string(4*level, ' '); - os << indent; - if(g.blocking()) os << '!'; - if(g.joinable()) os << 'J'; - os << (g.exclusive() ? "(|\n" : "(\n"); - for(const auto& p : g) { - print(os, p, level+1); - } - os << '\n' << indent << (g.exclusive() ? "|)" : ")"); - if(g.repeatable()) os << "..."; - os << '\n'; -} - - -} // namespace debug -} //namespace clipp - -#endif - diff --git a/include/exception.h b/include/exception.h deleted file mode 100644 index b630bd00..00000000 --- a/include/exception.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef UTIL_EXCEPTION_H -#define UTIL_EXCEPTION_H - -#include <exception> -#include <string> - -namespace eic::util { - class Exception : public std::exception { - public: - Exception(std::string_view msg, std::string_view type = "exception") : msg_{msg}, type_{type} {} - - virtual const char* what() const throw() { return msg_.c_str(); } - virtual const char* type() const throw() { return type_.c_str(); } - virtual ~Exception() throw() {} - - private: - std::string msg_; - std::string type_; - }; -} // namespace eic::util - -#endif diff --git a/include/mt.h b/include/mt.h deleted file mode 100644 index 198050c6..00000000 --- a/include/mt.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef MT_H -#define MT_H - -// Defines the number of threads to run within the ROOT analysis scripts. -// TODO: make this a file configured by the CI scripts so we can specify -// the number of threads (and the number of processes) at a global -// level - -constexpr const int kNumThreads = 8; - -#endif diff --git a/include/plot.h b/include/plot.h deleted file mode 100644 index c1986163..00000000 --- a/include/plot.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef PLOT_H -#define PLOT_H - -#include <TCanvas.h> -#include <TColor.h> -#include <TPad.h> -#include <TPaveText.h> -#include <TStyle.h> -#include <fmt/core.h> -#include <vector> - -namespace plot { - - const int kMpBlue = TColor::GetColor(0x1f, 0x77, 0xb4); - const int kMpOrange = TColor::GetColor(0xff, 0x7f, 0x0e); - const int kMpGreen = TColor::GetColor(0x2c, 0xa0, 0x2c); - const int kMpRed = TColor::GetColor(0xd6, 0x27, 0x28); - const int kMpPurple = TColor::GetColor(0x94, 0x67, 0xbd); - const int kMpBrown = TColor::GetColor(0x8c, 0x56, 0x4b); - const int kMpPink = TColor::GetColor(0xe3, 0x77, 0xc2); - const int kMpGrey = TColor::GetColor(0x7f, 0x7f, 0x7f); - const int kMpMoss = TColor::GetColor(0xbc, 0xbd, 0x22); - const int kMpCyan = TColor::GetColor(0x17, 0xbe, 0xcf); - - const std::vector<int> kPalette = {kMpBlue, kMpOrange, kMpGreen, kMpRed, kMpPurple, - kMpBrown, kMpPink, kMpGrey, kMpMoss, kMpCyan}; - - void draw_label(int ebeam, int pbeam, const std::string_view detector) - { - auto t = new TPaveText(.15, 0.800, .7, .925, "NB NDC"); - t->SetFillColorAlpha(kWhite, 0.4); - t->SetTextFont(43); - t->SetTextSize(25); - t->AddText(fmt::format("#bf{{{} }}SIMULATION", detector).c_str()); - t->AddText(fmt::format("{} GeV on {} GeV", ebeam, pbeam).c_str()); - t->SetTextAlign(12); - t->Draw(); - } - -} // namespace plot - -#endif diff --git a/include/util.h b/include/util.h deleted file mode 100644 index 6a24b293..00000000 --- a/include/util.h +++ /dev/null @@ -1,161 +0,0 @@ -#ifndef UTIL_H -#define UTIL_H - -// TODO: should probably be moved to a global benchmark utility library - -#include <algorithm> -#include <cmath> -#include <exception> -#include <fmt/core.h> -#include <limits> -#include <string> -#include <vector> - -#include <Math/Vector4D.h> - -#include "dd4pod/Geant4ParticleCollection.h" -#include "eicd/TrackParametersCollection.h" - -namespace util { - - // Exception definition for unknown particle errors - // FIXME: A utility exception base class should be included in the analysis - // utility library, so we can skip most of this boilerplate - class unknown_particle_error : public std::exception { - public: - unknown_particle_error(std::string_view particle) : m_particle{particle} {} - virtual const char* what() const throw() - { - return fmt::format("Unknown particle type: {}", m_particle).c_str(); - } - virtual const char* type() const throw() { return "unknown_particle_error"; } - - private: - const std::string m_particle; - }; - - // Simple function to return the appropriate PDG mass for the particles - // we care about for this process. - // FIXME: consider something more robust (maybe based on hepPDT) to the - // analysis utility library - inline double get_pdg_mass(std::string_view part) - { - if (part == "electron") { - return 0.0005109989461; - } else if (part == "muon") { - return .1056583745; - } else if (part == "jpsi") { - return 3.0969; - } else if (part == "upsilon") { - return 9.49630; - } else if (part == "proton"){ - return 0.938272; - } else { - throw unknown_particle_error{part}; - } - } - - // Get a vector of 4-momenta from raw tracking info, using an externally - // provided particle mass assumption. - inline auto momenta_from_tracking(const std::vector<eic::TrackParametersData>& tracks, - const double mass) - { - std::vector<ROOT::Math::PxPyPzMVector> momenta{tracks.size()}; - // transform our raw tracker info into proper 4-momenta - std::transform(tracks.begin(), tracks.end(), momenta.begin(), [mass](const auto& track) { - // make sure we don't divide by zero - if (fabs(track.qOverP) < 1e-9) { - return ROOT::Math::PxPyPzMVector{}; - } - const double p = fabs(1. / track.qOverP); - const double px = p * cos(track.phi) * sin(track.theta); - const double py = p * sin(track.phi) * sin(track.theta); - const double pz = p * cos(track.theta); - return ROOT::Math::PxPyPzMVector{px, py, pz, mass}; - }); - return momenta; - } - - // Get a vector of 4-momenta from the simulation data. - // TODO: Add PID selector (maybe using ranges?) - inline auto momenta_from_simulation(const std::vector<dd4pod::Geant4ParticleData>& parts) - { - std::vector<ROOT::Math::PxPyPzMVector> momenta{parts.size()}; - // transform our simulation particle data into 4-momenta - std::transform(parts.begin(), parts.end(), momenta.begin(), [](const auto& part) { - return ROOT::Math::PxPyPzMVector{part.psx, part.psy, part.psz, part.mass}; - }); - return momenta; - } - - // Find the decay pair candidates from a vector of particles (parts), - // with invariant mass closest to a desired value (pdg_mass) - inline std::pair<ROOT::Math::PxPyPzMVector, ROOT::Math::PxPyPzMVector> - find_decay_pair(const std::vector<ROOT::Math::PxPyPzMVector>& parts, const double pdg_mass) - { - int first = -1; - int second = -1; - double best_mass = -1; - - // go through all particle combinatorics, calculate the invariant mass - // for each combination, and remember which combination is the closest - // to the desired pdg_mass - for (size_t i = 0; i < parts.size(); ++i) { - for (size_t j = i + 1; j < parts.size(); ++j) { - const double new_mass{(parts[i] + parts[j]).mass()}; - if (fabs(new_mass - pdg_mass) < fabs(best_mass - pdg_mass)) { - first = i; - second = j; - best_mass = new_mass; - } - } - } - if (first < 0) { - return {{}, {}}; - } - return {parts[first], parts[second]}; - } - - // Calculate the magnitude of the momentum of a vector of 4-vectors - inline auto mom(const std::vector<ROOT::Math::PxPyPzMVector>& momenta) - { - std::vector<double> P(momenta.size()); - // transform our raw tracker info into proper 4-momenta - std::transform(momenta.begin(), momenta.end(), P.begin(), - [](const auto& mom) { return mom.P(); }); - return P; - } - // Calculate the transverse momentum of a vector of 4-vectors - inline auto pt(const std::vector<ROOT::Math::PxPyPzMVector>& momenta) - { - std::vector<double> pt(momenta.size()); - // transform our raw tracker info into proper 4-momenta - std::transform(momenta.begin(), momenta.end(), pt.begin(), - [](const auto& mom) { return mom.pt(); }); - return pt; - } - - // Calculate the azimuthal angle phi of a vector of 4-vectors - inline auto phi(const std::vector<ROOT::Math::PxPyPzMVector>& momenta) - { - std::vector<double> phi(momenta.size()); - // transform our raw tracker info into proper 4-momenta - std::transform(momenta.begin(), momenta.end(), phi.begin(), - [](const auto& mom) { return mom.phi(); }); - return phi; - } - // Calculate the pseudo-rapidity of a vector of particles - inline auto eta(const std::vector<ROOT::Math::PxPyPzMVector>& momenta) - { - std::vector<double> eta(momenta.size()); - // transform our raw tracker info into proper 4-momenta - std::transform(momenta.begin(), momenta.end(), eta.begin(), - [](const auto& mom) { return mom.eta(); }); - return eta; - } - - //========================================================================================================= - -} // namespace util - -#endif diff --git a/options/.gitignore b/options/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/options/env.sh b/options/env.sh deleted file mode 100755 index 4b7b1e76..00000000 --- a/options/env.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/bash - -## ============================================================================= -## Global configuration variables for the benchmark scripts -## The script defines the following environment variables that are meant to -## be overriden by the Gitlab continuous integration (CI) -## -## - JUGGLER_DETECTOR: detector package to be used for the benchmark -## - JUGGLER_N_EVENTS: #events processed by simulation/reconstruction -## - JUGGLER_INSTALL_PREFIX: location where Juggler (digi/recon) is installed -## - JUGGLER_N_THREADS: Number of threads/processes to spawn in parallel -## - JUGGLER_RNG_SEED: Random seed for the RNG -## -## It also defines the following additional variables for internal usage -## - LOCAL_PREFIX: prefix for packages installed during the benchmark -## - DETECTOR_PREFIX: prefix for the detector definitions -## - DETECTOR_PATH: actual path with the detector definitions -## -## Finally, it makes sure LOCAL_PREFIX and JUGGLER_PREFIX are added to PATH -## and LD_LIBRARY_PATH -## ============================================================================= - -echo "Setting up the Physics Benchmarks environment" - -## ============================================================================= -## Default variable definitions, normally these should be set -## by the CI. In case of local development you may want to change these -## in case you would like to modify the detector package or -## number of events to be analyzed during the benchmark - -## Detector package to be used during the benchmark process -if [ ! -n "${JUGGLER_DETECTOR}" ] ; then - export JUGGLER_DETECTOR="topside" -fi - -if [ ! -n "${JUGGLER_DETECTOR_VERSION}" ] ; then - export JUGGLER_DETECTOR_VERSION="master" -fi - - -## Number of events that will be processed by the reconstruction -if [ ! -n "${JUGGLER_N_EVENTS}" ] ; then - export JUGGLER_N_EVENTS=100 -fi - -## Maximum number of threads or processes a single pipeline should use -## (this is not enforced, but the different pipeline scripts should use -## this to guide the number of parallel processes or threads they -## spawn). -if [ ! -n "${JUGGLER_N_THREADS}" ]; then - export JUGGLER_N_THREADS=10 -fi - -## Random seed for event generation, should typically not be changed for -## reproductability. -if [ ! -n "${JUGGLER_RNG_SEED}" ]; then - export JUGGLER_RNG_SEED=1 -fi - -## Install prefix for juggler, needed to locate the Juggler xenv files. -## Also used by the CI as install prefix for other packages where needed. -## You should not have to touch this. Note that for local usage a different -## prefix structure is automatically used. -if [ ! -n "${JUGGLER_INSTALL_PREFIX}" ] ; then - export JUGGLER_INSTALL_PREFIX="/usr/local" -fi -## Ensure the juggler prefix is an absolute path -export JUGGLER_INSTALL_PREFIX=`realpath ${JUGGLER_INSTALL_PREFIX}` - - -## Location of local data for pass data from job to job within pipeline. -## Not saved as artifacts. -if [ ! -n "${LOCAL_DATA_PATH}" ] ; then - export LOCAL_DATA_PATH="/scratch/${CI_PROJECT_NAME}_${CI_PIPELINE_ID}" -fi - -## ============================================================================= -## Other utility variables that govern how some of the dependent packages -## are built and installed. You should not have to change these. - -## local prefix to be used for local storage of packages -## downloaded/installed during the benchmark process -LOCAL_PREFIX=".local" -mkdir -p ${LOCAL_PREFIX} -export LOCAL_PREFIX=`realpath ${LOCAL_PREFIX}` - -## detector prefix: prefix for the detector definitions -export DETECTOR_PREFIX="${LOCAL_PREFIX}/detector" -mkdir -p ${DETECTOR_PREFIX} - -## detector path: actual detector definition path -export DETECTOR_PATH="${DETECTOR_PREFIX}/${JUGGLER_DETECTOR}" - -## build dir for ROOT to put its binaries etc. -export ROOT_BUILD_DIR=$LOCAL_PREFIX/root_build - -echo "JUGGLER_DETECTOR: ${JUGGLER_DETECTOR}" -echo "JUGGLER_DETECTOR_VERSION: ${JUGGLER_DETECTOR_VERSION}" -echo "JUGGLER_N_EVENTS: ${JUGGLER_N_EVENTS}" -echo "JUGGLER_N_THREADS: ${JUGGLER_N_THREADS}" -echo "JUGGLER_RNG_SEED: ${JUGGLER_RNG_SEED}" -echo "JUGGLER_INSTALL_PREFIX: ${JUGGLER_INSTALL_PREFIX}" -echo "LOCAL_DATA_PATH: ${LOCAL_DATA_PATH}" -echo "LOCAL_PREFIX: ${LOCAL_PREFIX}" -echo "DETECTOR_PREFIX: ${DETECTOR_PREFIX}" -echo "DETECTOR_PATH: ${DETECTOR_PATH}" -echo "ROOT_BUILD_DIR: ${ROOT_BUILD_DIR}" - -## ============================================================================= -## Setup PATH and LD_LIBRARY_PATH to include our prefixes -echo "Adding JUGGLER_INSTALL_PREFIX and LOCAL_PREFIX to PATH and LD_LIBRARY_PATH" -export PATH=${JUGGLER_INSTALL_PREFIX}/bin:${LOCAL_PREFIX}/bin:${PATH} -export LD_LIBRARY_PATH=${JUGGLER_INSTALL_PREFIX}/lib:${LOCAL_PREFIX}/lib:${LD_LIBRARY_PATH} - -## ============================================================================= -## That's all! -echo "Environment setup complete." diff --git a/tools/dev-shell b/tools/dev-shell deleted file mode 100755 index 0295f14b..00000000 --- a/tools/dev-shell +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash - -## ============================================================================= -## Setup (if needed) and start a development shell environment on Linux or MacOS -## ============================================================================= - -## make sure we launch this script from the project root directory -PROJECT_ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"/.. -pushd ${PROJECT_ROOT} - -## We do not load the global development environment here, as this script is -## to be executed on a "naked" system outside of any container - -## ============================================================================= -## Step 1: Parse command line options - -## do we want to force-update the container (only affects Linux) -## default: we do not want to do this. -FORCE_UPDATE= - -function print_the_help { - echo "USAGE: ./util/start_dev_shell [-f]" - echo "OPTIONS:" - echo " -f,--force Force-update container (Only affects Linux)" - echo " -h,--help Print this message" - echo "" - echo " This script will setup and launch a containerized development - environment" - exit -} -while [ $# -gt 0 ] -do - key="$1" - case $key in - -f|--force) - FORCE_UPDATE="true" - shift # past value - ;; - -h|--help) - print_the_help - shift - ;; - *) # unknown option - echo "unknown option $1" - exit 1 - ;; - esac -done - -## get OS type -OS=`uname -s` - -## ============================================================================= -## Step 2: Update container and launch shell -echo "Launching a containerized development shell" - -case ${OS} in - Linux) - echo " - Detected OS: Linux" - ## Use the same prefix as we use for other local packages - export PREFIX=.local/lib - if [ ! -f $PREFIX/juggler_latest.sif ] || [ ! -z ${FORCE_UPDATE} ]; then - echo " - Fetching singularity image" - mkdir -p $PREFIX - wget https://eicweb.phy.anl.gov/eic/juggler/-/jobs/artifacts/master/raw/build/juggler.sif?job=singularity:latest -O $PREFIX/juggler_latest.sif - fi - echo " - Using singularity to launch shell..." - singularity exec $PREFIX/juggler_latest.sif eic-shell - ;; - Darwin) - echo " - Detector OS: MacOS" - echo " - Syncing docker container" - docker pull sly2j/juggler:latest - echo " - Using docker to launch shell..." - docker run -v /Users:/Users -w=$PWD -i -t --rm sly2j/juggler:latest eic-shell - ;; - *) - echo "ERROR: dev shell not available for this OS (${OS})" - exit 1 -esac - -## ============================================================================= -## Step 3: All done -echo "Exiting development environment..." diff --git a/tools/download.sh b/tools/download.sh deleted file mode 100755 index 47be238b..00000000 --- a/tools/download.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash - -## ============================================================================= -## Download generator & reconstruction artifacts for one or more physics -## processes. -## ============================================================================= - -## make sure we launch this script from the project root directory -PROJECT_ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"/.. -pushd ${PROJECT_ROOT} - -PROCS=() -BRANCH="master" - -function print_the_help { - echo "USAGE: -p process [-p process2] [-b git_branch]" - echo "OPTIONS:" - echo " -p,--process Physics process name (can be defined multiple - times)." - echo " -b,--branch Git branch to download artifacts from (D: - $BRANCH)" - echo " -h,--help Print this message" - echo "" - echo " This script will download the relevant generator artifacts needed" - echo " for local testing of the benchmarks." - exit -} - -while [ $# -gt 0 ] -do - key="$1" - case $key in - -p|--process) - PROCS+=("$2") - shift # past argument - shift # past value - ;; - -b|--branch) - BRANCH="$2" - shift # past argument - shift # past value - ;; - -h|--help) - print_the_help - shift - ;; - *) # unknown option - echo "unknown option: $1" - exit 1 - ;; - esac -done - -echo "Downloading generator & reconstruction artifacts for one or more physics processes" - -if [ ${#PROCS[@]} -eq 0 ]; then - echo "ERROR: need one or more processes: -p <process name> " - exit 1 -fi - -for proc in ${PROCS[@]}; do - echo "Dowloading artifacts for $proc (branch: $BRANCH)" - wget https://eicweb.phy.anl.gov/EIC/benchmarks/physics_benchmarks/-/jobs/artifacts/$BRANCH/download?job=${proc}:generate -O results_gen.zip - ## FIXME this needs to be smarter, probably through more flags... - wget https://eicweb.phy.anl.gov/EIC/benchmarks/physics_benchmarks/-/jobs/artifacts/$BRANCH/download?job=${proc}:process -O results_rec.zip - echo "Unpacking artifacts..." - unzip -u -o results_gen.zip - unzip -u -o results_rec.zip - echo "Cleaning up..." - rm results_???.zip -done -popd -echo "All done" diff --git a/util/build_detector.sh b/util/build_detector.sh deleted file mode 100755 index a12d3db9..00000000 --- a/util/build_detector.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash - -## ============================================================================= -## Build and install the JUGGLER_DETECTOR detector package into our local prefix -## ============================================================================= - -## make sure we launch this script from the project root directory -PROJECT_ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"/.. -pushd ${PROJECT_ROOT} - -## ============================================================================= -## Load the environment variables. To build the detector we need the following -## variables: -## -## - JUGGLER_DETECTOR: the detector package we want to use for this benchmark -## - LOCAL_PREFIX: location where local packages should be installed -## - DETECTOR_PREFIX: prefix for the detector definitions -## - DETECTOR_PATH: full path for the detector definitions -## this is the same as ${DETECTOR_PREFIX}/${JUGGLER_DETECTOR} -## -## You can read options/env.sh for more in-depth explanations of the variables -## and how they can be controlled. -source options/env.sh - -## ============================================================================= -## Step 1: download/update the detector definitions (if needed) -pushd ${DETECTOR_PREFIX} - -## We need an up-to-date copy of the detector -## start clean to avoid issues... -if [ -d ${JUGGLER_DETECTOR} ]; then - echo "cleaning up ${JUGGLER_DETECTOR}" - rm -rf ${JUGGLER_DETECTOR} -fi -echo "Fetching ${JUGGLER_DETECTOR}" -git clone -b ${JUGGLER_DETECTOR_VERSION} https://eicweb.phy.anl.gov/EIC/detectors/${JUGGLER_DETECTOR}.git -#else - #echo "Updating ${JUGGLER_DETECTOR}" - #pushd ${JUGGLER_DETECTOR} - #git pull --ff-only - #popd -#fi -## We also need an up-to-date copy of the accelerator. For now this is done -## manually. Down the road we could maybe automize this with cmake -if [ -d accelerator ]; then - echo "cleaning up accelerator" - rm -rf accelerator -fi -echo "Fetching accelerator" -git clone https://eicweb.phy.anl.gov/EIC/detectors/accelerator.git -#else -# echo "Updating accelerator" -# pushd accelerator -# git pull --ff-only -# popd -#fi -## Now symlink the accelerator definition into the detector definition -echo "Linking accelerator definition into detector definition" -ln -s -f ${DETECTOR_PREFIX}/accelerator/eic ${DETECTOR_PATH}/eic - -## ============================================================================= -## Step 2: Compile and install the detector definition -echo "Building and installing the ${JUGGLER_DETECTOR} package" - -mkdir -p ${DETECTOR_PREFIX}/build -pushd ${DETECTOR_PREFIX}/build -cmake ${DETECTOR_PATH} -DCMAKE_INSTALL_PREFIX=${LOCAL_PREFIX} -DCMAKE_CXX_STANDARD=17 && make -j30 install || exit 1 -cmake ${DETECTOR_PATH} -DCMAKE_INSTALL_PREFIX=${LOCAL_PREFIX} -DCMAKE_CXX_STANDARD=17 && make -j30 install - -## ============================================================================= -## Step 3: That's all! -echo "Detector build/install complete!" diff --git a/util/collect_benchmarks.py b/util/collect_benchmarks.py deleted file mode 100755 index 0af7e9a1..00000000 --- a/util/collect_benchmarks.py +++ /dev/null @@ -1,181 +0,0 @@ -#!/usr/bin/env python3 - -""" -Combine the json files from the individual benchmark tests into -a final master json file combining all benchmarks. - -Benchmark results are expected to be all json files in the results -directory. -""" - -## Our master definition file, the benchmark project directory -MASTER_FILE=r'benchmarks/benchmarks.json' - -## Our results directory -RESULTS_PATH=r'results' - -## Output json file with all benchmark results -OUTPUT_FILE=r'results/summary.json' - -import argparse -import json -from pathlib import Path - -## Exceptions for this module -class Error(Exception): - '''Base class for exceptions in this module.''' - pass -class FileNotFoundError(Error): - '''File does not exist. - - Attributes: - file: the file name - message: error message - ''' - def __init__(self, file): - self.file = file - self.message = 'No such file or directory: {}'.format(file) - -class InvalidDefinitionError(Error): - '''Raised for missing keys in the definitions. - - Attributes: - key: the missing key - file: the definition file - message: error message - ''' - def __init__(self, key, file): - self.key = key - self.file = file - self.message = "key '{}' not found in '{}'".format(key, file) - -class InvalidResultError(Error): - '''Raised for invalid benchmark result value. - - Attributes: - key: the missing key - value: the invalid value - file: the benchmark definition file - message: error message - ''' - def __init__(self, key, value, file): - self.key = key - self.value = value - self.file = file - self.message = "value '{}' for key '{}' invalid in benchmark file '{}'".format( - value, key, file) - -def collect_benchmarks(): - '''Collect all benchmark results and write results to a single file.''' - print("Collecting all benchmark results") - - ## load the test definition for this benchmark - results = _load_master() - - ## collect the test results - results['benchmarks'] = _load_benchmarks() - - ## calculate aggregate test statistics - results = _aggregate_results(results) - - ## save results to output file - _save(results) - - ## Summarize results - for bm in results['benchmarks']: - _print_benchmark(bm) - _print_summary(results) - -def _load_master(): - '''Load master definition.''' - master_file = Path(MASTER_FILE) - if not master_file.exists(): - raise FileNotFoundError(master_file) - print(' --> Loading master definition from:', master_file) - results = None - with master_file.open() as f: - results = json.load(f) - ## ensure this is a valid benchmark file - for key in ('name', 'title', 'description'): - if not key in results: - raise InvalidDefinitionError('target', master_file) - return results - -def _load_benchmarks(): - '''Load all benchmark results from the results folder.''' - print(' --> Collecting all benchmarks') - rootdir = Path(RESULTS_PATH) - results = [] - for file in rootdir.glob('*.json'): - print(' --> Loading file:', file, '... ', end='') - with open(file) as f: - bm = json.load(f) - ## skip files that don't include test results - if not 'tests' in bm: - print('skipped (does not contain benchmark results).') - continue - ## check if these are valid benchmark results, - ## raise exception otherwise - for key in ('name', 'title', 'description', 'target', 'n_tests', - 'n_pass', 'n_fail', 'n_error', 'maximum', 'sum', 'value', - 'result'): - if not key in bm: - raise InvalidDefinitionError(key, file) - if bm['result'] not in ('pass', 'fail', 'error'): - raise InvalidResultError('result', bm['result'], file) - ## Append to our test results - results.append(bm) - print('done') - return results - -def _aggregate_results(results): - '''Aggregate benchmark results.''' - print(' --> Aggregating benchmark statistics') - results['n_benchmarks'] = len(results['benchmarks']) - results['n_pass'] = len([1 for t in results['benchmarks'] if t['result'] == 'pass']) - results['n_fail'] = len([1 for t in results['benchmarks'] if t['result'] == 'fail']) - results['n_error'] = len([1 for t in results['benchmarks'] if t['result'] == 'error']) - if results['n_error'] > 0: - results['result'] = 'error' - elif results['n_fail'] == 0: - results['result'] = 'pass' - else: - results['result'] = 'fail' - return results - -def _save(results): - '''Save aggregated benchmark results''' - ofile = Path(OUTPUT_FILE) - print(' --> Saving results to:', ofile) - with ofile.open('w') as f: - json.dump(results, f, indent=4) - -def _print_benchmark(bm): - '''Print benchmark summary to the terminal.''' - print('====================================================================') - print(' Summary for:', bm['title']) - print(' Pass: {}, Fail: {}, Error: {} out of {} total tests'.format( - bm['n_pass'], bm['n_fail'], bm['n_error'], - bm['n_tests'])) - print(' Weighted sum: {} / {}'.format(bm['sum'], bm['maximum'])) - print(' kBenchmark value: {} (target: {})'.format( - bm['value'], bm['target'])) - print(' ===> status:', bm['result']) - -def _print_summary(results): - '''Print master benchmark summary to the terminal.''' - print('====================================================================') - print('MASTER BENCHMARK SUMMARY FOR:', results['title'].upper()) - print('Pass: {}, Fail: {}, Error: {} out of {} total benchmarks'.format( - results['n_pass'], results['n_fail'], results['n_error'], - results['n_benchmarks'])) - print('===> status:', results['result']) - print('====================================================================') - - -if __name__ == "__main__": - try: - collect_benchmarks() - except Error as e: - print() - print('ERROR', e.message) diff --git a/util/collect_tests.py b/util/collect_tests.py deleted file mode 100755 index 4d860ca7..00000000 --- a/util/collect_tests.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env python3 - -""" -Collect the json files from individual benchmark tests into -a larger json file that combines all benchmark information, -and do additional accounting for the benchmark. - -Tests results are expected to have the following file name and directory -structure: - results/<BENCHMARK_NAME>/**/<SOME_NAME>.json -where ** implies we check recursively check all sub-directories of <BENCHMARK_NAME> - -Internally, we will look for the "tests" keyword in each of these -files to identify them as benchmark components. -""" - -## Our benchmark definition file, stored in the benchmark root directory -BENCHMARK_FILE=r'benchmarks/{}/benchmark.json' - -## Our benchmark results directory -RESULTS_PATH=r'results/{}' - -## Output json file with benchmark results -OUTPUT_FILE=r'results/{}.json' - -import argparse -import json -from pathlib import Path - -## Exceptions for this module -class Error(Exception): - '''Base class for exceptions in this module.''' - pass -class FileNotFoundError(Exception): - '''File does not exist. - - Attributes: - file: the file name - message: error message - ''' - def __init__(self, file): - self.file = file - self.message = 'No such file or directory: {}'.format(file) - -class InvalidBenchmarkDefinitionError(Exception): - '''Raised for missing keys in the benchmark definition. - - Attributes: - key: the missing key - file: the benchmark definition file - message: error message - ''' - def __init__(self, key, file): - self.key = key - self.file = file - self.message = "key '{}' not found in benchmark file '{}'".format(key, file) - -class InvalidTestDefinitionError(Exception): - '''Raised for missing keys in the test result. - - Attributes: - key: the missing key - file: the test result file - message: error message - ''' - def __init__(self, key, file): - self.key = key - self.file = file - self.message = "key '{}' not found in test file '{}'".format(key, file) -class InvalidTestResultError(Exception): - '''Raised for invalid test result value. - - Attributes: - key: the missing key - value: the invalid value - file: the benchmark definition file - message: error message - ''' - def __init__(self, key, value, file): - self.key = key - self.value = value - self.file = file - self.message = "value '{}' for key '{}' invalid in test file '{}'".format( - value, key, file) - - -parser = argparse.ArgumentParser() -parser.add_argument( - 'benchmark', - action='append', - help='One or more benchmarks for which to collect test results.') - -def collect_results(benchmark): - '''Collect benchmark tests and write results to file.''' - print("Collecting results for benchmark '{}'".format(benchmark)) - - ## load the test definition for this benchmark - results = _load_benchmark(benchmark) - - ## collect the test results - results['tests'] = _load_tests(benchmark) - - ## calculate aggregate test statistics - results = _aggregate_results(results) - - ## save results to output file - _save(benchmark, results) - - ## Summarize results - _print_summary(results) - -def _load_benchmark(benchmark): - '''Load benchmark definition.''' - benchfile = Path(BENCHMARK_FILE.format(benchmark)) - if not benchfile.exists(): - raise FileNotFoundError(benchfile) - print(' --> Loading benchmark definition from:', benchfile) - results = None - with benchfile.open() as f: - results = json.load(f) - ## ensure this is a valid benchmark file - for key in ('name', 'title', 'description', 'target'): - if not key in results: - raise InvalidBenchmarkDefinitionError('target', benchfile) - return results - -def _load_tests(benchmark): - '''Loop over all test results in benchmark folder and return results.''' - print(' --> Collecting all test results') - rootdir = Path(RESULTS_PATH.format(benchmark)) - results = [] - for file in rootdir.glob('**/*.json'): - print(' --> Loading file:', file, '... ', end='') - with open(file) as f: - new_results = json.load(f) - ## skip files that don't include test results - if not 'tests' in new_results: - print('not a test result') - continue - ## check if these are valid test results, - ## raise exception otherwise - for test in new_results['tests']: - for key in ('name', 'title', 'description', 'quantity', 'target', - 'value', 'result'): - if not key in test: - raise InvalidTestDefinitionError(key, file) - if test['result'] not in ('pass', 'fail', 'error'): - raise InvalidTestResultError('result', test['result'], file) - ## ensure 'weight' key present, defaulting to 1 in needed - if not 'weight' in test: - test['weight'] = 1. - ## Append to our test results - results.append(test) - print('done') - return results - -def _aggregate_results(results): - '''Aggregate test results for our benchmark.''' - print(' --> Aggregating benchmark statistics') - results['target'] = float(results['target']) - results['n_tests'] = len(results['tests']) - results['n_pass'] = len([1 for t in results['tests'] if t['result'] == 'pass']) - results['n_fail'] = len([1 for t in results['tests'] if t['result'] == 'fail']) - results['n_error'] = len([1 for t in results['tests'] if t['result'] == 'error']) - results['maximum'] = sum([t['weight'] for t in results['tests']]) - results['sum'] = sum([t['weight'] for t in results['tests'] if t['result'] == 'pass']) - if (results['n_tests'] > 0): - results['value'] = results['sum'] / results['maximum'] - if results['n_error'] > 0: - results['result'] = 'error' - elif results['value'] >= results['target']: - results['result'] = 'pass' - else: - results['result'] = 'fail' - else: - results['value'] = -1 - results['result'] = 'error' - return results - -def _save(benchmark, results): - '''Save benchmark results''' - ofile = Path(OUTPUT_FILE.format(benchmark)) - print(' --> Saving benchmark results to:', ofile) - with ofile.open('w') as f: - json.dump(results, f, indent=4) - -def _print_summary(results): - '''Print benchmark summary to the terminal.''' - print('====================================================================') - print('Summary for:', results['title']) - print('Pass: {}, Fail: {}, Error: {} out of {} total tests'.format( - results['n_pass'], results['n_fail'], results['n_error'], - results['n_tests'])) - print('Weighted sum: {} / {}'.format(results['sum'], results['maximum'])) - print('Benchmark value: {} (target: {})'.format( - results['value'], results['target'])) - print('===> status:', results['result']) - print('====================================================================') - - -if __name__ == "__main__": - args = parser.parse_args() - for benchmark in args.benchmark: - collect_results(benchmark) diff --git a/util/compile_analyses.py b/util/compile_analyses.py deleted file mode 100755 index 153f2ea2..00000000 --- a/util/compile_analyses.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python3 - -""" -Compile all root analysis scripts under -benchmarks/<BENCHMARK>/analysis/*.cxx - -Doing this step here rather than during the main benchmark script has -multiple advantages: - 1. Get feedback on syntax errors early on, without wasting compute resources - 2. Avoid race conditions for large benchmarks run in parallel - 3. Make it easier to properly handle the root build directory, as - this has to exist prior to our attempt to compile, else all will - fail (this is probably an old bug in root...) - -Analysis scripts are expected to have extension 'cxx' and be located in the analysis -subdirectory -""" - -## Our analysis path and file extension for glob -ANALYSIS_PATH=r'benchmarks/{}/analysis' -ANALYSIS_EXT = r'cxx' - -import argparse -import os -from pathlib import Path - -## Exceptions for this module -class Error(Exception): - '''Base class for exceptions in this module.''' - pass - -class PathNotFoundError(Exception): - '''Path does not exist. - - Attributes: - path: the path name - message: error message - ''' - def __init__(self, path): - self.file = file - self.message = 'No such directory: {}'.format(file) -class NoAnalysesFoundError(Exception): - '''Did not find any analysis scripts to complile - - Attributes: - path: the analysis path - message: error message - ''' - def __init__(self, path): - self.file = file - self.message = 'No analysis found (extension \'{}\' in path: {}'.format(file, - ANALYSIS_EXT) - -class CompilationError(Exception): - '''Raised when we failed to compile an analysis script - - Attributes: - file: analysis file name - path: analysis path - message: error message - ''' - def __init__(self, file): - self.file = file - self.message = "Analysis '{}' failed to compile".format(file) - -parser = argparse.ArgumentParser() -parser.add_argument( - 'benchmark', - help='A benchmarks for which to compile the analysis scripts.') - -def compile_analyses(benchmark): - '''Compile all analysis scripts for a benchmark.''' - print("Compiling all analyis scripts for '{}'".format(benchmark)) - - ## Ensure our build directory exists - _init_build_dir(benchmark) - - ## Get a list of all analysis scripts - _compile_all(benchmark) - - ## All done! - print('All analyses for', benchmark, 'compiled successfully') - -def _init_build_dir(benchmark): - '''Initialize our ROOT build directory (if using one).''' - print(' --> Initializing ROOT build directory ...') - build_prefix = os.getenv('ROOT_BUILD_DIR') - if build_prefix is None: - print(' --> ROOT_BUILD_DIR not set, no action needed.') - return - ## deduce the root build directory - pwd = os.getenv('PWD') - build_dir = '{}/{}/{}'.format(build_prefix, pwd, ANALYSIS_PATH.format(benchmark)) - print(" --> Ensuring directory '{}' exists".format(build_dir)) - os.system('mkdir -p {}'.format(build_dir)) - -def _compile_all(benchmark): - '''Compile all analysis for this benchmark.''' - print(' --> Compiling analysis scripts') - anadir = Path(ANALYSIS_PATH.format(benchmark)) - if not anadir.exists(): - raise PathNotFoundError(anadir) - ana_list = [] - for file in anadir.glob('*.{}'.format(ANALYSIS_EXT)): - ana_list.append(file) - print(' --> Compiling:', file, flush=True) - err = os.system(_compile_cmd(file)) - if err: - raise CompilationError(file) - if len(ana_list) == 0: - raise NoAnalysesFoundError(anadir) - -def _compile_cmd(file): - '''Return a one-line shell command to compile an analysis script.''' - return r'bash -c "root -q -b -e \".L {}+\""'.format(file) - -if __name__ == "__main__": - args = parser.parse_args() - compile_analyses(args.benchmark) diff --git a/util/parse_cmd.sh b/util/parse_cmd.sh deleted file mode 100755 index 04028d89..00000000 --- a/util/parse_cmd.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/bash - -## ============================================================================= -## Generic utility script to parse command line arguments for the various -## bash scripts that control the CI. This script should be source'd with -## command line arguments from a bash-like (non-POSIX) shell such as -## bash or zsh. -## -## To control some of the functionality of the script, you can set the following -## environment variables prior to calling the script: -## - REQUIRE_DECAY: require the --decay flag to be set -## ============================================================================= - -## Commented out because this should be taken care of by the -## calling script to not enforce a fixed directory structure. -## make sure we launch this script from the project root directory -#PROJECT_ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"/.. -#pushd ${PROJECT_ROOT} - -## ============================================================================= -## Step 1: Process the command line arguments - -function print_the_help { - echo "USAGE: --ebeam E --pbeam E --config C1 --decay D2" - echo " [--config C2 --decay D2 --decay D3 ...]" - echo "REQUIRED ARGUMENTS:" - echo " --ebeam Electron beam energy" - echo " --pbeam Ion beam energy" - echo " --config Generator configuration identifiers (at least one)" - if [ ! -z ${REQUIRE_DECAY} ]; then - echo " --decay Specific decay particle (e.g. muon)." - fi - if [ ! -z ${REQUIRE_LEADING} ]; then - echo " --leading Leading particle of interest (e.g. jpsi)." - fi - echo " -h,--help Print this message" - echo "" - echo " Generate multiple monte carlo samples for a desired process." - exit -} - -## Required variables -EBEAM= -PBEAM= -DECAYS= -CONFIG= - -while [ $# -gt 0 ] -do - key="$1" - case $key in - --config) - CONFIG="$2" - shift # past argument - shift # past value - ;; - --ebeam) - EBEAM="$2" - shift # past argument - shift # past value - ;; - --pbeam) - PBEAM="$2" - shift # past argument - shift # past value - ;; - --leading) - LEADING="$2" - shift # past argument - shift # past value - ;; - --decay) - DECAY="$2" - shift # past argument - shift # past value - ;; - -h|--help) - print_the_help - exit 0 - ;; - *) # unknown option - echo "unknown option" - exit 1 - ;; - esac -done - -if [ -z $CONFIG ]; then - echo "ERROR: CONFIG not defined: --config <config>" - print_the_help - exit 1 -elif [ -z $EBEAM ]; then - echo "ERROR: EBEAM not defined: --ebeam <energy>" - print_the_help - exit 1 -elif [ -z $PBEAM ]; then - echo "ERROR: PBEAM not defined: --pbeam <energy>" - print_the_help - exit 1 -elif [ -z $LEADING ] && [ ! -z $REQUIRE_LEADING ]; then - echo "ERROR: LEADING not defined: --leading <channel>" - print_the_help - exit 1 -elif [ ! -z $LEADING ] && [ -z $REQUIRE_LEADING ]; then - echo "ERROR: LEADING flag specified but not required" - print_the_help - exit 1 -elif [ -z $DECAY ] && [ ! -z $REQUIRE_DECAY ]; then - echo "ERROR: DECAY not defined: --decay <channel>" - print_the_help - exit 1 -elif [ ! -z $DECAY ] && [ -z $REQUIRE_DECAY ]; then - echo "ERROR: DECAY flag specified but not required" - print_the_help - exit 1 -fi - -## Export the configured variables -export CONFIG -export EBEAM -export PBEAM -if [ ! -z $REQUIRE_LEADING ]; then - export LEADING -fi -if [ ! -z $REQUIRE_DECAY ]; then - export DECAY -fi diff --git a/util/print_env.sh b/util/print_env.sh deleted file mode 100755 index 7a4a8935..00000000 --- a/util/print_env.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -echo "JUGGLER_TAG: ${JUGGLER_TAG}" -echo "JUGGLER_DETECTOR: ${JUGGLER_DETECTOR}" -echo "JUGGLER_DETECTOR_VERSION: ${JUGGLER_DETECTOR_VERSION}" -echo "JUGGLER_N_EVENTS: ${JUGGLER_N_EVENTS}" -echo "JUGGLER_N_THREADS: ${JUGGLER_N_THREADS}" -echo "JUGGLER_RNG_SEED: ${JUGGLER_RNG_SEED}" -echo "JUGGLER_INSTALL_PREFIX: ${JUGGLER_INSTALL_PREFIX}" -echo "LOCAL_DATA_PATH: ${LOCAL_DATA_PATH}" -echo "LOCAL_PREFIX: ${LOCAL_PREFIX}" -echo "DETECTOR_PREFIX: ${DETECTOR_PREFIX}" -echo "DETECTOR_PATH: ${DETECTOR_PATH}" diff --git a/util/run_many.py b/util/run_many.py deleted file mode 100755 index ccb7e83a..00000000 --- a/util/run_many.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python3 - -""" -This script will run a CI generator or processing script for multiple configurations. - -Author: Sylvester Joosten <sjoosten@anl.gov> -""" - -import os -import argparse -from multiprocessing import Pool, get_context -from tempfile import NamedTemporaryFile - -class InvalidArgumentError(Exception): - pass - -parser = argparse.ArgumentParser() -parser.add_argument( - 'command', - help="Script to be launched in parallel") -parser.add_argument( - '--energy', '-e', - dest='energies', - action='append', - help='One or more beam energy pairs (e.g. 10x100)', - required=True) -parser.add_argument( - '--config', '-c', - dest='configs', - action='append', - help='One or more configurations', - required=True) -parser.add_argument( - '--leading', - dest='leads', - action='append', - help='One or more leading particles(opt.)', - required=False) -parser.add_argument( - '--decay', - dest='decays', - action='append', - help='One or more decay channels (opt.)', - required=False) -parser.add_argument( - '--nproc', - dest='nproc', - default=5, - type=int, - help='Number of processes to launch in parallel', - required=False) - -def worker(command): - '''Execute the command in a system call, with the supplied argument string.''' - ## use a temporary file to capture the terminal output, and then - ## print the terminal output once the command finishes - with NamedTemporaryFile() as f: - cmd = [command, ' 2>&1 >', f.name] - cmd = ' '.join(cmd) - print("Executing '{}'".format(cmd)) - ret = os.system(cmd) - with open(f.name) as log: - print(log.read()) - return ret - -if __name__ == '__main__': - args = parser.parse_args() - print('Launching CI script in parallel for multiple settings') - for e in args.energies: - beam_setting = e.split('x') - if not beam_setting[0].isnumeric() or not beam_setting[1].isnumeric(): - print("Error: invalid beam energy setting:", e) - raise InvalidArgumentError - - if not os.path.exists(args.command): - print("Error: Script not found:", args.command) - raise InvalidArgumentError - - if args.nproc < 1 or args.nproc > 50: - print("Error: Invalid process limit (should be 1-50):", args.nproc) - raise InvalidArgumentError - - print(' - command: {}'.format(args.command)) - print(' - energies: {}'.format(args.energies)) - print(' - config: {}'.format(args.configs)) - print(' - nproc: {}'.format(args.nproc)) - if (args.leads): - print(' - leading: {}'.format(args.leads)) - if (args.decays): - print(' - decay: {}'.format(args.decays)) - - ## Expand our command and argument list for all combinatorics - cmds = [] - decays = args.decays if args.decays else [None] - leads = args.leads if args.leads else [None] - for e in args.energies: - for c in args.configs: - for l in leads: - for d in decays: - beam_setting = e.split('x') - cmd = [args.command, - '--ebeam', beam_setting[0], - '--pbeam', beam_setting[1], - '--config', c] - if l is not None: - cmd += ['--leading', l] - if d is not None: - cmd += ['--decay', d] - cmds.append(' '.join(cmd)) - - ## create a process pool - ## note that I'm using themultiprocessing.get_context function to setup - ## a context where subprocesses are created using the new "spawn" process - ## which avoids deadlocks that sometimes happen in the default dispatch - with get_context('spawn').Pool(processes=args.nproc) as pool: - return_values = pool.map(worker, cmds) - ## check if we all exited nicely, else exit with status 1 - if not all(ret == 0 for ret in return_values): - n_fail = sum([1 for ret in return_values if ret != 0]) - print('ERROR, {} of {} jobs failed'.format(n_fail, len(cmds))) - print('Return values:', [ret for ret in return_values if ret != 0]) - exit(1) - - ## That's all! -- GitLab