R3BROOT
R3B analysis software
Loading...
Searching...
No Matches
R3BProgramOptions.h
Go to the documentation of this file.
1#pragma once
2
3#include <boost/program_options.hpp>
4#include <boost/program_options/options_description.hpp>
5#include <boost/program_options/positional_options.hpp>
6#include <boost/program_options/variables_map.hpp>
7#include <cstdlib>
8#include <fmt/core.h>
9#include <iostream>
10#include <memory>
11#include <optional>
12#include <stdexcept>
13#include <string>
14#include <string_view>
15#include <type_traits>
16#include <unordered_map>
17#include <utility>
18
19namespace R3B
20{
21 namespace po = boost::program_options;
22
23 // TODO: C++20 use concepts
24 template <typename>
25 struct is_bool : std::false_type
26 {
27 };
28
29 template <>
30 struct is_bool<bool> : std::true_type
31 {
32 };
33
34 template <typename T, typename = void>
35 struct IsString : std::false_type
36 {
37 };
38
39 template <typename T>
40 struct IsString<T, std::void_t<decltype(std::string_view{ std::declval<T>() })>> : std::true_type
41 {
42 };
43
44 template <typename T>
46
47 class OptionConcept;
48 template <typename Type>
49 class Option;
50
51 template <typename Type>
52 class OptionHandle;
53
55 {
56 public:
57 ProgramOptions() = default;
58 explicit ProgramOptions(const std::string& desc)
59 : desc_{ desc }
60 {
61 }
62
63 template <typename OptionType>
64 auto create_option(const std::string& optionName,
65 const std::string& option_desc,
66 std::optional<OptionType> defaultValue = {}) -> decltype(auto)
67 {
68 if (auto search = registries_.find(optionName); search != registries_.end())
69 {
70 std::cerr << "ERROR: option has been already defined!" << std::endl;
71 exit(1);
72 }
73 auto option = std::make_unique<Option<OptionType>>(optionName, defaultValue.value_or(OptionType{}), this);
74 option->add(option_desc, not defaultValue.has_value());
75 registries_.emplace(optionName, option.get());
76 return OptionHandle{ std::move(option) };
77 }
78
79 auto verify(int argc, char** argv) -> bool;
80 void delete_option(const std::string& optionName) { registries_.erase(optionName); }
81 auto get_posDescRef() -> auto& { return pos_desc_; }
82 auto get_desc_ref() -> auto& { return desc_; }
83
84 private:
85 std::unordered_map<std::string, OptionConcept*> registries_;
86 po::positional_options_description pos_desc_;
87 po::options_description desc_;
88 po::variables_map varMap_;
89 };
90
92 {
93 public:
94 OptionConcept(const OptionConcept&) = delete;
96 auto operator=(const OptionConcept&) -> OptionConcept& = delete;
98 virtual ~OptionConcept() = default;
99 OptionConcept() = default;
100 virtual void retrieve(const po::variables_map& varMap) = 0;
101 };
102
103 template <typename Type>
104 class Option : public OptionConcept
105 {
106 public:
107 using type = Type;
108 Option(const Option&) = delete;
109 Option(Option&&) = delete;
110 auto operator=(const Option&) -> Option& = delete;
111 auto operator=(Option&&) -> Option& = delete;
112 Option(std::string name, Type defaultValue, ProgramOptions* program)
113 : name_{ std::move(name) }
114 , value_{ std::move(defaultValue) }
115 , program_{ program }
116 {
117 if (auto end = name_.find(','))
118 {
119 key_ = name_.substr(0, end);
120 }
121 else
122 {
123 key_ = name_;
124 }
125 }
126 ~Option() override { program_->delete_option(name_); }
127
128 void add(const std::string& desc, bool is_requried = false)
129 {
130 auto& po_desc = program_->get_desc_ref();
131 is_required_ = is_requried;
132
133 auto desc_full = fmt::format("{} [ required ]", desc);
134 if (not is_required_)
135 {
136 desc_full = fmt::format("{} [ = {} ]", desc, value_);
137 if constexpr (IsString_v<Type>)
138 {
139 desc_full = fmt::format("{} [ = {:?} ]", desc, value_);
140 }
141 }
142
143 if constexpr (is_bool<Type>::value)
144 {
145 po_desc.add_options()(name_.c_str(), desc_full.c_str());
146 }
147 else
148 {
149 po_desc.add_options()(name_.c_str(), po::value<Type>(), desc_full.c_str());
150 }
151 }
152
153 void as_positional(int option)
154 {
155 program_->get_posDescRef().add(key_.c_str(), option);
156 is_positional_ = true;
157 }
158
159 void retrieve(const po::variables_map& varMap) override
160 {
161 if (varMap.count(key_) != 0U)
162 {
163
164 if constexpr (is_bool<Type>::value)
165 {
166 value_ = true;
167 }
168 else
169 {
170 value_ = varMap[key_].template as<Type>();
171 }
172 }
173 else if (is_required_)
174 {
175 const auto error_msg = fmt::format(R"(Program option "--{}" is required! )", name_);
176 throw std::runtime_error(error_msg);
177 }
178 }
179
180 void set_required(bool p_rq = true) { is_required_ = p_rq; }
181
182 [[nodiscard]] auto value() const { return value_; }
183 [[nodiscard]] auto is_required() const -> bool { return is_required_; }
184 [[nodiscard]] auto is_positional() const -> bool { return is_positional_; }
185
186 private:
187 bool is_positional_ = false;
188 bool is_required_ = false;
189 std::string name_;
190 std::string key_;
191 std::string desc_;
192 Type value_{};
194 };
195
196 template <typename Type>
198 {
199 public:
200 explicit OptionHandle(std::unique_ptr<Option<Type>> option)
201 : option_{ std::move(option) }
202 {
203 }
204
205 auto value() const { return option_->value(); }
206 auto operator()() const { return option_->value(); }
208 {
209 option_->as_positional(pos);
210 return std::move(*this);
211 }
212 auto operator->() -> Option<Type>* { return option_.get(); }
213
214 private:
215 std::unique_ptr<Option<Type>> option_ = nullptr;
216 };
217
218}; // namespace R3B
auto operator=(const OptionConcept &) -> OptionConcept &=delete
virtual ~OptionConcept()=default
OptionConcept()=default
OptionConcept(OptionConcept &&)=delete
OptionConcept(const OptionConcept &)=delete
virtual void retrieve(const po::variables_map &varMap)=0
auto operator=(OptionConcept &&) -> OptionConcept &=delete
auto operator->() -> Option< Type > *
std::unique_ptr< Option< Type > > option_
auto make_positional(int pos) -> OptionHandle< Type > &&
OptionHandle(std::unique_ptr< Option< Type > > option)
~Option() override
ProgramOptions * program_
auto value() const
void add(const std::string &desc, bool is_requried=false)
Option(const Option &)=delete
auto is_positional() const -> bool
auto operator=(Option &&) -> Option &=delete
void set_required(bool p_rq=true)
auto operator=(const Option &) -> Option &=delete
std::string key_
auto is_required() const -> bool
Option(Option &&)=delete
std::string name_
std::string desc_
void retrieve(const po::variables_map &varMap) override
void as_positional(int option)
Option(std::string name, Type defaultValue, ProgramOptions *program)
auto get_desc_ref() -> auto &
po::options_description desc_
ProgramOptions(const std::string &desc)
ProgramOptions()=default
auto get_posDescRef() -> auto &
auto verify(int argc, char **argv) -> bool
po::positional_options_description pos_desc_
void delete_option(const std::string &optionName)
std::unordered_map< std::string, OptionConcept * > registries_
po::variables_map varMap_
auto create_option(const std::string &optionName, const std::string &option_desc, std::optional< OptionType > defaultValue={}) -> decltype(auto)
constexpr bool IsString_v