22#define RFL_CLI_READER_HPP_
33
44#include < charconv>
5- #include < concepts>
65#include < clocale>
6+ #include < concepts>
77#include < cstdlib>
88#include < map>
99#include < optional>
@@ -26,9 +26,10 @@ inline constexpr char path_separator = '.';
2626// / Example: `--ports=8080,8081,8082` for an array of ports.
2727inline constexpr char array_delimiter = ' ,' ;
2828
29- // / Represents a CLI variable that can be a direct value or a path in the argument map.
30- // / The `path` represents the hierarchical key (e.g., "database.host").
31- // / The `direct_value` is used when parsing array elements directly.
29+ // / Represents a CLI variable that can be a direct value or a path in the
30+ // / argument map. The `path` represents the hierarchical key (e.g.,
31+ // / "database.host"). The `direct_value` is used when parsing array elements
32+ // / directly.
3233struct CliVarType {
3334 const std::map<std::string, std::string>* const args = nullptr ;
3435 const std::string path;
@@ -55,10 +56,10 @@ struct CliArrayType {
5556// / @param _str The string to parse
5657// / @param _path The CLI argument path (unused for strings)
5758// / @return The string unchanged
58- template <class T > requires std::same_as<T, std::string>
59- rfl::Result<T> parse_value (
60- const std::string& _str, const std::string&
61- ) noexcept {
59+ template <class T >
60+ requires std::same_as<T, std::string>
61+ rfl::Result<T> parse_value ( const std::string& _str,
62+ const std::string& ) noexcept {
6263 return _str;
6364}
6465
@@ -68,22 +69,23 @@ rfl::Result<T> parse_value(
6869// / @param _str The string to parse
6970// / @param _path The CLI argument path (for error messages)
7071// / @return A Result containing the boolean value or an error
71- template <class T > requires std::same_as<T, bool >
72- rfl::Result<T> parse_value (
73- const std::string& _str, const std::string& _path
74- ) noexcept {
72+ template <class T >
73+ requires std::same_as<T, bool >
74+ rfl::Result<T> parse_value ( const std::string& _str,
75+ const std::string& _path ) noexcept {
7576 if (_str.empty () || _str == " true" || _str == " 1" ) {
7677 return true ;
7778 }
7879 if (_str == " false" || _str == " 0" ) {
7980 return false ;
8081 }
81- return error (
82- " Could not cast ' " + _str + " ' to boolean for key ' " + _path + " '." );
82+ return error (" Could not cast ' " + _str + " ' to boolean for key ' " + _path +
83+ " '." );
8384}
8485
8586// / Parses a string value to a floating-point number.
86- // / Uses locale-independent parsing (C locale) to ensure "3.14" works consistently.
87+ // / Uses locale-independent parsing (C locale) to ensure "3.14" works
88+ // / consistently.
8789// / @tparam T Must be a floating-point type (float, double, long double)
8890// / @param _str The string to parse
8991// / @param _path The CLI argument path (for error messages)
@@ -92,10 +94,10 @@ rfl::Result<T> parse_value(
9294// std::strtod depends on the C locale (LC_NUMERIC), so "3.14" can fail
9395// under locales that use comma as decimal separator.
9496// Use strtod_l with an explicit "C" locale on all platforms for consistency.
95- template <class T > requires (std::is_floating_point_v<T>)
96- rfl::Result <T> parse_value(
97- const std::string& _str, const std::string& _path
98- ) noexcept {
97+ template <class T >
98+ requires (std::is_floating_point_v <T>)
99+ rfl::Result<T> parse_value ( const std::string& _str,
100+ const std::string& _path ) noexcept {
99101 char * end = nullptr ;
100102#ifdef _WIN32
101103 const auto c_locale = _create_locale (LC_NUMERIC, " C" );
@@ -107,8 +109,8 @@ rfl::Result<T> parse_value(
107109 freelocale (c_locale);
108110#endif
109111 if (end != _str.c_str () + _str.size ()) {
110- return error (
111- " Could not cast ' " + _str + " ' to floating point for key ' " + _path + " '." );
112+ return error (" Could not cast ' " + _str + " ' to floating point for key ' " +
113+ _path + " '." );
112114 }
113115 return static_cast <T>(value);
114116}
@@ -119,23 +121,24 @@ rfl::Result<T> parse_value(
119121// / @param _str The string to parse
120122// / @param _path The CLI argument path (for error messages)
121123// / @return A Result containing the parsed integer or an error
122- template <class T > requires (std::is_integral_v<T> && !std::same_as<T, bool >)
123- rfl::Result <T> parse_value(
124- const std::string& _str, const std::string& _path
125- ) noexcept {
124+ template <class T >
125+ requires (std::is_integral_v <T> && !std::same_as<T, bool >)
126+ rfl::Result<T> parse_value ( const std::string& _str,
127+ const std::string& _path ) noexcept {
126128 T value;
127129 const auto [ptr, ec] =
128130 std::from_chars (_str.data (), _str.data () + _str.size (), value);
129131 if (ec != std::errc () || ptr != _str.data () + _str.size ()) {
130- return error (
131- " Could not cast ' " + _str + " ' to integer for key ' " + _path + " '." );
132+ return error (" Could not cast ' " + _str + " ' to integer for key ' " + _path +
133+ " '." );
132134 }
133135 return value;
134136}
135137
136138// / Reader for command-line interface arguments.
137- // / Parses hierarchical key-value pairs from CLI arguments (e.g., --database.host=localhost).
138- // / Supports arrays through comma-delimited values (e.g., --ports=8080,8081).
139+ // / Parses hierarchical key-value pairs from CLI arguments (e.g.,
140+ // / --database.host=localhost). Supports arrays through comma-delimited values
141+ // / (e.g., --ports=8080,8081).
139142struct Reader {
140143 using InputArrayType = CliArrayType;
141144 using InputObjectType = CliObjectType;
@@ -147,31 +150,32 @@ struct Reader {
147150 // / Gets a specific element from a CLI array by index.
148151 // / @param _idx The index of the element to retrieve
149152 // / @param _arr The CLI array
150- // / @return A Result containing the element as a CliVarType or an error if out of bounds
153+ // / @return A Result containing the element as a CliVarType or an error if out
154+ // / of bounds
151155 rfl::Result<InputVarType> get_field_from_array (
152156 const size_t _idx, const InputArrayType& _arr) const noexcept {
153157 if (_idx >= _arr.values .size ()) {
154- return error (
155- std::string ( " Index " ) + std::to_string (_idx) + " out of bounds." );
158+ return error (std::string ( " Index " ) + std::to_string (_idx) +
159+ " out of bounds." );
156160 }
157161 return InputVarType{nullptr , " " , _arr.values [_idx]};
158162 }
159163
160164 // / Gets a specific field from a CLI object by name.
161- // / Constructs a child path by appending the field name to the object's prefix.
165+ // / Constructs a child path by appending the field name to the object's
166+ // / prefix.
162167 // / @param _name The field name
163168 // / @param _obj The CLI object
164169 // / @return A Result containing a CliVarType for accessing the field
165170 rfl::Result<InputVarType> get_field_from_object (
166171 const std::string& _name, const InputObjectType& _obj) const noexcept {
167- const auto child_path = _obj.prefix .empty ()
168- ? _name
169- : _obj.prefix + _name;
172+ const auto child_path = _obj.prefix .empty () ? _name : _obj.prefix + _name;
170173 return InputVarType{_obj.args , child_path, std::nullopt };
171174 }
172175
173176 // / Checks if a CLI variable is empty (has no value).
174- // / A variable is empty if there's no direct value and no matching key in the argument map.
177+ // / A variable is empty if there's no direct value and no matching key in the
178+ // / argument map.
175179 // / @param _var The CLI variable to check
176180 // / @return true if the variable is empty, false otherwise
177181 bool is_empty (const InputVarType& _var) const noexcept {
@@ -186,23 +190,21 @@ struct Reader {
186190 }
187191 const auto prefix = _var.path + path_separator;
188192 const auto it = _var.args ->lower_bound (prefix);
189- return it == _var.args ->end ()
190- || it->first .substr (0 , prefix.size ()) != prefix;
193+ return it == _var.args ->end () ||
194+ it->first .substr (0 , prefix.size ()) != prefix;
191195 }
192196
193197 // / Reads all elements from a CLI array using the provided array reader.
194- // / @tparam ArrayReader The type of reader that processes individual array elements
198+ // / @tparam ArrayReader The type of reader that processes individual array
199+ // / elements
195200 // / @param _array_reader The reader object that processes each element
196201 // / @param _arr The CLI array to read from
197202 // / @return std::nullopt on success, or an Error if reading fails
198203 template <class ArrayReader >
199- std::optional<Error> read_array (
200- const ArrayReader& _array_reader,
201- const InputArrayType& _arr
202- ) const noexcept {
204+ std::optional<Error> read_array (const ArrayReader& _array_reader,
205+ const InputArrayType& _arr) const noexcept {
203206 for (const auto & val : _arr.values ) {
204- const auto err = _array_reader.read (
205- InputVarType{nullptr , " " , val});
207+ const auto err = _array_reader.read (InputVarType{nullptr , " " , val});
206208 if (err) {
207209 return err;
208210 }
@@ -211,36 +213,33 @@ struct Reader {
211213 }
212214
213215 // / Reads all fields from a CLI object using the provided object reader.
214- // / Iterates through all arguments with the object's prefix and extracts child field names.
215- // / @tparam ObjectReader The type of reader that processes individual object fields
216+ // / Iterates through all arguments with the object's prefix and extracts child
217+ // / field names.
218+ // / @tparam ObjectReader The type of reader that processes individual object
219+ // / fields
216220 // / @param _object_reader The reader object that processes each field
217221 // / @param _obj The CLI object to read from
218222 // / @return std::nullopt on success, or an Error if reading fails
219223 template <class ObjectReader >
220- std::optional<Error> read_object (
221- const ObjectReader& _object_reader,
222- const InputObjectType& _obj
223- ) const noexcept {
224+ std::optional<Error> read_object (const ObjectReader& _object_reader,
225+ const InputObjectType& _obj) const noexcept {
224226 std::set<std::string> seen;
225- auto it = _obj.prefix .empty ()
226- ? _obj.args ->begin ()
227- : _obj.args ->lower_bound (_obj.prefix );
227+ auto it = _obj.prefix .empty () ? _obj.args ->begin ()
228+ : _obj.args ->lower_bound (_obj.prefix );
228229 while (it != _obj.args ->end ()) {
229- if (!_obj.prefix .empty ()
230- && it->first .substr (0 , _obj.prefix .size ()) != _obj.prefix ) {
230+ if (!_obj.prefix .empty () &&
231+ it->first .substr (0 , _obj.prefix .size ()) != _obj.prefix ) {
231232 break ;
232233 }
233234 const auto rest = std::string_view (it->first ).substr (_obj.prefix .size ());
234235 const auto separator_pos = rest.find (path_separator);
235- const auto child = std::string (
236- separator_pos == std::string_view::npos
237- ? rest
238- : rest.substr (0 , separator_pos));
236+ const auto child = std::string (separator_pos == std::string_view::npos
237+ ? rest
238+ : rest.substr (0 , separator_pos));
239239 if (!child.empty () && seen.insert (child).second ) {
240240 const auto child_path = _obj.prefix + child;
241- _object_reader.read (
242- std::string_view (child),
243- InputVarType{_obj.args , child_path, std::nullopt });
241+ _object_reader.read (std::string_view (child),
242+ InputVarType{_obj.args , child_path, std::nullopt });
244243 }
245244 ++it;
246245 }
@@ -279,14 +278,12 @@ struct Reader {
279278 rfl::Result<InputObjectType> to_object (
280279 const InputVarType& _var) const noexcept {
281280 if (!_var.args ) {
282- return error (" Cannot convert to object: no argument map available"
283- + (_var.path .empty ()
284- ? std::string (" ." )
285- : " for key '" + _var.path + " '." ));
281+ return error (" Cannot convert to object: no argument map available" +
282+ (_var.path .empty () ? std::string (" ." )
283+ : " for key '" + _var.path + " '." ));
286284 }
287- const auto prefix = _var.path .empty ()
288- ? std::string (" " )
289- : _var.path + path_separator;
285+ const auto prefix =
286+ _var.path .empty () ? std::string (" " ) : _var.path + path_separator;
290287 return InputObjectType{_var.args , prefix};
291288 }
292289
@@ -295,16 +292,13 @@ struct Reader {
295292 // / @param _var The CLI variable (unused)
296293 // / @return Always returns an error
297294 template <class T >
298- rfl::Result<T> use_custom_constructor (
299- const InputVarType&
300- ) const noexcept {
295+ rfl::Result<T> use_custom_constructor (const InputVarType&) const noexcept {
301296 return error (" Custom constructors are not supported for CLI parsing." );
302297 }
303298
304299 private:
305300 static std::optional<std::string> get_value (
306- const InputVarType& _var
307- ) noexcept {
301+ const InputVarType& _var) noexcept {
308302 if (_var.direct_value ) {
309303 return *_var.direct_value ;
310304 }
@@ -318,9 +312,7 @@ struct Reader {
318312 return it->second ;
319313 }
320314
321- static std::vector<std::string> split (
322- const std::string& _str, char _delim
323- ) {
315+ static std::vector<std::string> split (const std::string& _str, char _delim) {
324316 std::vector<std::string> result;
325317 if (_str.empty ()) {
326318 return result;
@@ -341,7 +333,6 @@ struct Reader {
341333 }
342334 return result;
343335 }
344-
345336};
346337
347338} // namespace rfl::cli
0 commit comments