SB++
Sandbox applications
Loading...
Searching...
No Matches
arguments.hpp
1#pragma once
10
11#include <map>
12#include "shared.hpp"
13
14
15namespace arg {
16
17 // Custom Policy
18 // FALSE: If there are valid values, they are are enforced.
19 // TRUE: The user can provide custom values outside of the valid ones.
20 // MODIFIABLE: Valid values are needed, but can be modified via VALUE:MOD
21 enum struct custom_policy {FALSE, TRUE, MODIFIABLE};
22
23
27 typedef struct config {
28 const std::string& l_name;
29 const std::string& s_name;
30 const std::string& def = "false";
31 const std::string& mod = "";
32 const shared::vector valid = {};
33 const bool& must_set = false;
34 const bool& flag_set = false;
35 const custom_policy& custom = custom_policy::FALSE;
36 const bool& list = false;
37 const uint_fast8_t& position = -1;
38 const std::string& help;
39 const bool& updates_sof = false;
41
42
46 class Arg {
47 private:
48
49 // Long name, like --verbose, short-name, like -v
50 std::string long_name, short_name;
51
52 // What the default value is.
53 std::string value, def, modifier;
54
55 // The help string!
56 std::string help;
57
58 // A path for if wildcards are encountered.
59 bool list = false, update_sof = false;
60
61 // The set of valid values. Empty means any value is acceptable.
62 // list_val is only used when list=true.
63 shared::set valid = {}, list_val = {};
64 shared::vector order = {};
65
66 // If the argument is mandatory, it must be set.
67 // Flags combine the properties of single value arguments
68 // and lists. They can either be toggled on/off, or you
69 // can provide a list of additional modifiers.
70 bool mandatory = false, set = false, flag = false;
71
72 // The custom policy.
73 custom_policy custom = custom_policy::FALSE;
74
75 // Set a mandatory position for the argument.
76 uint_fast8_t positional = -1;
77
78 // Special logic to parse command lines!
79 std::function<std::string(const std::string_view&)> parser, m_parser;
80
81
89 bool digest_keypair(const std::string_view& key, const std::string_view& val, const uint_fast8_t& pos) {
90 // Single value modifiers are easy to split.
91 if (!list && custom == custom_policy::MODIFIABLE && val.contains(':')) {
92
93 // Split on colons.
94 auto s = container::init<shared::vector>(container::split<shared::vector, char>, val, ':', false);
95 if (s.size() < 2) throw std::runtime_error("Invalid modifier: " + std::string(val));
96
97 // Grab the modifier keypair.
98 auto v = s[0]; auto mod = val.substr(val.find(':') + 1);
99
100 // If there are no valids, or it is valid, modify.
101 if (valid.size() <= 1 || valid.contains(v)) {
102 value = parser(v);
103 modifier = m_parser(mod);
104 return true;
105 }
106 }
107
108 // Ensure the key matches this argument
109 else if (key == long_name || key == short_name) {
110 if (long_name == "--help") throw std::runtime_error("Help!");
111
112 // Using ! sets value to their default values, and clears list.
113 if (val == "!") {
114 value = def;
115 modifier = "";
116 list_val.clear();
117 return true;
118 }
119
120 // Ensure the value is valid.
121 else if (valid.size() <= 1 || custom == custom_policy::TRUE || std::find(valid.begin(), valid.end(), val) != valid.end()) {
122
123 if (list || flag) {
124 // If our key already has multiple values, split and handle separately.
125 if (val.contains(',')) {
126 for (const auto& x : container::init<shared::vector>(container::split<shared::vector, char>, val, ',', false))
127 digest_keypair(key, x, pos);
128 }
129
130 // Otherwise just update.
131 else list_val.emplace(parser(val));
132 }
133 else value = parser(val);
134 return true;
135 }
136
137 // Flags can be increment by just passing true, even if "true" isn't a valid value for the flagset.
138 else if (flag && val == "true") return true;
139 }
140 return false;
141 }
142
143
153 void increment() {
154 auto i = level();
155 if (i + 1 < valid.size())
156 value = order[i + 1];
157 else
158 std::cerr << long_name << ": Already at highest level!" << std::endl;
159 }
160
161
166 uint_fast8_t level(const std::string_view& val) const {return std::find(order.begin(), order.end(), val) - order.begin();}
167
168
169 public:
170
171
179 const config& c,
180 const std::function<std::string(const std::string_view&)>& handler = [](const std::string_view& value){return std::string(value);},
181 const std::function<std::string(const std::string_view&)>& m_handler = [](const std::string_view& value){return std::string(value);}
182 ) {
183
184 long_name = c.l_name;
185 short_name = c.s_name;
186 help = c.help;
187 parser = handler;
188 m_parser = m_handler;
189 update_sof = c.updates_sof;
190 flag = c.flag_set;
191
192 // Parse the defaults.
193 if (!c.mod.empty()) modifier = m_parser(c.mod);
194 value = parser(c.def);
195 def = value;
196
197 // Construct the valid field.
198 // The default value will always be valid, and can be excluded from the passed
199 // valid values.
200 valid = {c.def}; order = {c.def};
201 for (const auto& v : c.valid) {
202 if (v != c.def) {valid.emplace(v); order.emplace_back(v);}
203 }
204 mandatory = c.must_set;
205 custom = c.custom;
206 list = c.list;
207 positional = c.position;
208 }
209
210
217 bool digest(const shared::vector& args, uint_fast8_t& x) {
218
219 // Get the value
220 const auto& key = args[x];
221 auto ret = false;
222
223 if (key == "--help") throw std::runtime_error("Help!");
224 if (key == "--version") throw std::runtime_error("Version");
225
226 // If a .sb script ends with a list argument, new args
227 // may be "consumed" by the list. However, we need to append
228 // arguments to the end for proper overriding. Scripts, therefore
229 // use "-- $@" to ensure that new arguments are not attached to a
230 // list
231 if (key == "--") return true;
232
233 // If we have an equal, it's `key=value`
234 if (key.contains("=")) {
235 auto keypair = container::init<shared::vector>(container::split<shared::vector, char>, key, '=', false);
236 ret = digest_keypair(keypair[0], keypair[1], x);
237 }
238
239 // If we have a short name, digest each character.
240 // There is no support for values for short names.
241 else if (key[0] == '-' && key[1] != '-' && key.length() > 2) {
242 for (uint_fast8_t x = 1; x < key.length(); ++x) {
243 if (key[x] == short_name[1]) {
244 increment();
245 }
246 }
247 // We don't return true here because with a collection of args we
248 // need to parse for each match.
249 }
250
251 // / Otherwise, if the next argument isn't a switch, we have `key value`
252 else if (!key.empty() && (key == long_name || key == short_name)) {
253 if (x + 1 != args.size() && !args[x].empty() && args[x + 1][0] != '-') {
254 if (list || flag) {
255 while (++x < args.size() && !args[x].empty() && args[x][0] != '-')
256 digest_keypair(key, args[x], x);
257 ret = true;
258 --x;
259 }
260 else ret = digest_keypair(key, args[++x], x);
261 }
262
263 else if (list) throw std::runtime_error("List argument requires values: " + long_name);
264
265 // 'Increment' the flag. Flags use the set bool to check if it's been set.
266 else if (flag) ret = true;
267
268 // If there are stages to the switch, increment it.
269 else if (valid.size() > 1) {
270 increment();
271 ret = true;
272 }
273 }
274
275 // If the position matches, and nothing else did, assume the key is the value.
276 else if (positional == x) {
277 if (key.starts_with("-"))
278 throw std::runtime_error("Invalid argument passed to: " + long_name);
279 value = parser(key);
280 ret = true;
281 }
282
283 // Update the set flag.
284 set |= ret;
285
286 if (x == positional && !ret)
287 throw std::runtime_error("Positional argument not set: " + long_name);
288 return ret;
289 }
290
291
296 void update() {
297 if (mandatory && !set) throw std::runtime_error("Missing mandatory argument: " + long_name);
298 value = parser(value); modifier = m_parser(modifier);
299 }
300
301
306 std::string get_help() const {
307 std::stringstream help_text;
308 help_text << long_name;
309 if (!short_name.empty()) help_text << '/' << short_name;
310 help_text << ' ';
311
312 if (list) {
313 help_text << '[' << "VAL";
314 if (custom == custom_policy::MODIFIABLE) help_text << ":MOD";
315 help_text << ',';
316 }
317 if (order.size() > 1) help_text << '{';
318 if (order.size() > 1) {
319 for (const auto& v : order) {
320 help_text << v;
321 if (custom == custom_policy::MODIFIABLE) help_text << ":MOD";
322 if (v != *std::prev(order.end())) help_text << ',';
323 }
324 help_text << '}';
325 }
326 if (list) help_text << "...]";
327
328 help_text << "\n\t" << help << '\n';
329 return help_text.str();
330 }
331
332
338 auto&& get(this auto&& self) {
339 if (self.list) throw std::runtime_error("Not a discrete value: " + self.long_name);
340 return self.value;
341 }
342
343
353 void emplace(const std::string& val) {set |= digest_keypair(long_name, val, -1);}
354
360 auto&& mod(this auto&& self) {return self.modifier;}
361
362
367 const bool& is_list() const {return list;}
368
369
374 const bool& is_flagset() const {return flag;}
375
376
381 uint_fast8_t level() const {return level(value);}
382
383
389 auto&& get_list(this auto&& self) {
390 if (!self.list && !self.flag) throw std::runtime_error("Argument must be a list: " + self.long_name);
391 return self.list_val;
392 }
393
398 auto&& get_valid(this auto&& self) {return self.valid;}
399
405 std::vector<std::pair<std::string, std::string>> get_modlist() const {
406 if (!list) throw std::runtime_error("Argument is not a list: " + long_name);
407 if (custom != custom_policy::MODIFIABLE) throw std::runtime_error("List must allowed modifiers!");
408
409 std::vector<std::pair<std::string, std::string>> ret;
410 for (const auto& val : list_val) {
411 if (val.contains(':')) {
412 auto s = container::init<shared::vector>(container::split<shared::vector, char>, val, ':', false);
413 ret.emplace_back(std::pair{s[0], s[1]});
414 }
415 else ret.emplace_back(std::pair{val, ""});
416 }
417 return ret;
418 }
419
420 const bool& updates_sof() const {return update_sof;}
421
422
427 const uint_fast8_t& position() const {return positional;}
428
433 operator bool() const {return level() != 0 || (flag && set) || (list && !list_val.empty());}
434
440 const bool operator < (const std::string_view& val) const {return level(value) < level(val);}
441
442
448 const bool operator >= (const std::string_view& val) const {return level(value) >= level(val);}
449 };
450
451
452 // Mapping of all switches.
453 extern std::map<std::string, arg::Arg> switches;
454
455 // Unknown arguments.
456 extern shared::vector unknown;
457
458 // The arguments.
459 extern shared::vector args;
460
461 extern std::string hash;
462
463
464 // Helper functions.
465 inline Arg& at(const std::string& key) {
466 if (!switches.contains(key)) throw std::runtime_error("Invalid argument" + key);
467 else return switches.at(key);
468 }
469 inline const std::string& get(const std::string& key) {return at(key).get();}
470 inline void emplace(const std::string& key, const std::string& val) {at(key).emplace(val);}
471 inline const std::string& mod(const std::string& key) {return at(key).mod();}
472 inline uint_fast8_t level(const std::string& key) {return at(key).level();}
473 inline const shared::set& list(const std::string& key) {return at(key).get_list();}
474 inline const shared::set& valid(const std::string& key) {return at(key).get_valid();}
475 inline const bool& is_list(const std::string& key) {return at(key).is_list();}
476 inline std::vector<std::pair<std::string, std::string>> modlist(const std::string& key) {return at(key).get_modlist();}
477
481 void parse_args();
482}
void update()
Update configurations.
Definition arguments.hpp:296
std::string get_help() const
Get the help text for the argument.
Definition arguments.hpp:306
uint_fast8_t level() const
Return the current level of the argument.
Definition arguments.hpp:381
std::vector< std::pair< std::string, std::string > > get_modlist() const
Return all values paired with their modifiers.
Definition arguments.hpp:405
Arg(const config &c, const std::function< std::string(const std::string_view &)> &handler=[](const std::string_view &value){return std::string(value);}, const std::function< std::string(const std::string_view &)> &m_handler=[](const std::string_view &value){return std::string(value);})
Construct an argument.
Definition arguments.hpp:178
const bool & is_list() const
Return whether the argument is a list.
Definition arguments.hpp:367
auto && mod(this auto &&self)
Get a mutable reference to the stored modifier.
Definition arguments.hpp:360
void emplace(const std::string &val)
Emplace a value.
Definition arguments.hpp:353
auto && get_list(this auto &&self)
Return a mutable reference to each unique value passed to the argument.
Definition arguments.hpp:389
const bool operator<(const std::string_view &val) const
Check if the current value is underneath the provided.
Definition arguments.hpp:440
auto && get(this auto &&self)
Get a mutable reference to the stored value.
Definition arguments.hpp:338
auto && get_valid(this auto &&self)
Return a set of all valid values.
Definition arguments.hpp:398
const uint_fast8_t & position() const
Return the position of the argument.
Definition arguments.hpp:427
const bool operator>=(const std::string_view &val) const
Check if the current value meets the provided.
Definition arguments.hpp:448
const bool & is_flagset() const
Return whether the argument is a flagset.
Definition arguments.hpp:374
bool digest(const shared::vector &args, uint_fast8_t &x)
Digest arguments.
Definition arguments.hpp:217
A general purpose, flexible command-line argument handler. This file includes definitions to create a...
Definition arguments.cpp:11
void parse_args()
Parse command line arguments.
Definition arguments.cpp:286
struct arg::config config
Definition arguments.hpp:27