Polyhedral-net Splines
Loading...
Searching...
No Matches
simple_arg.hpp
1// simple_arg.hpp
2#pragma once
3
4#include <algorithm>
5#include <iostream>
6#include <map>
7#include <sstream>
8#include <stdexcept>
9#include <string>
10#include <type_traits>
11#include <vector>
12
18struct SimpleArg {
23 struct Opt {
28 char s{};
33 std::string l;
38 std::string desc;
43 std::string default_str;
48 std::string type_name;
53 bool takes_val{};
58 bool required{};
63 std::vector<std::string> choices;
64 };
65
70 std::string prog;
71
76 std::vector<Opt> opts;
77
82 std::vector<Opt> positionals;
83
88 std::map<std::string,std::string> vals;
89
94 std::map<std::string,bool> flags;
95
100 std::vector<std::string> errs;
101
107 SimpleArg(std::string p) : prog(std::move(p)) {
108 add<bool>('h', "help", "show this help message");
109 }
110
111 // — register named args of any type T —
123 template<typename T>
124 void add(char s,
125 std::string l,
126 std::string d,
127 bool req = false,
128 T def = T{},
129 std::vector<std::string> choices = {})
130 {
131 Opt o;
132 o.s = s;
133 o.l = std::move(l);
134 o.desc = std::move(d);
135 o.required = req;
136 o.choices = std::move(choices);
137
138 if constexpr(std::is_same_v<T,bool>) {
139 o.takes_val = false;
140 o.type_name = "flag";
141 flags[o.l] = false;
142 } else {
143 o.takes_val = true;
144 if (!req) {
145 if constexpr(std::is_same_v<T,std::string>) o.default_str = def;
146 else o.default_str = std::to_string(def);
147 if (!o.default_str.empty())
148 vals[o.l] = o.default_str;
149 }
150 if (!o.choices.empty()) o.type_name = "enum";
151 else if constexpr(std::is_integral_v<T>) o.type_name = "int";
152 else if constexpr(std::is_floating_point_v<T>) o.type_name = "float";
153 else o.type_name = "string";
154 }
155 opts.push_back(std::move(o));
156 }
157
158 // — register positional args (always required) —
167 template<typename T>
168 void addPositional(std::string l,
169 std::string d,
170 std::vector<std::string> choices = {})
171 {
172 Opt o;
173 o.s = '\0';
174 o.l = std::move(l);
175 o.desc = std::move(d);
176 o.takes_val = true;
177 o.required = true;
178 o.choices = std::move(choices);
179
180 if (!o.choices.empty()) o.type_name = "enum";
181 else if constexpr(std::is_integral_v<T>) o.type_name = "int";
182 else if constexpr(std::is_floating_point_v<T>) o.type_name = "float";
183 else o.type_name = "string";
184
185 positionals.push_back(std::move(o));
186 }
187
188 // — parse everything, stopping at first error —
196 bool parse(int argc, char** argv) {
197 std::vector<std::string> raw_pos;
198
199 for (int i = 1; i < argc; ++i) {
200 std::string a = argv[i];
201
202 // — long form: --name or --name=val —
203 if (a.rfind("--",0) == 0) {
204 auto eq = a.find('=');
205 std::string name = a.substr(2, (eq==std::string::npos? a.size()-2: eq-2));
206 std::string val;
207 if (eq != std::string::npos) val = a.substr(eq+1);
208
209 auto it = std::find_if(opts.begin(), opts.end(),
210 [&](auto &o){ return o.l == name; });
211 if (it == opts.end()) {
212 errs.push_back("unknown option --" + name);
213 return false;
214 }
215 if (!it->takes_val) {
216 flags[name] = true;
217 } else {
218 if (eq == std::string::npos) {
219 if (i+1 < argc) val = argv[++i];
220 else {
221 errs.push_back("--" + name + " requires a value");
222 return false;
223 }
224 }
225 if (!store(*it, name, val)) return false;
226 }
227 }
228
229 // — short form: -x, -xyz, -xval, -xyval, etc. —
230 else if (a.size() > 1 && a[0]=='-' && a[1] != '-') {
231 // peel off one short‐opt at a time:
232 for (size_t pos = 1; pos < a.size(); ) {
233 char s = a[pos++];
234 auto it = std::find_if(opts.begin(), opts.end(),
235 [&](auto &o){ return o.s == s; });
236 if (it == opts.end()) {
237 errs.push_back(std::string("unknown option -") + s);
238 return false;
239 }
240 if (!it->takes_val) {
241 flags[it->l] = true;
242 continue;
243 }
244 // this option takes a value:
245 std::string val;
246 if (pos < a.size()) {
247 // e.g. "-n3" or "-oout.txt"
248 val = a.substr(pos);
249 } else {
250 // e.g. "-n 3"
251 if (i+1 < argc) val = argv[++i];
252 else {
253 errs.push_back(std::string("-") + s + " requires a value");
254 return false;
255 }
256 }
257 if (!store(*it, it->l, val)) return false;
258 break; // stop parsing this arg
259 }
260 }
261
262 // — positional —
263 else {
264 raw_pos.push_back(a);
265 }
266 }
267
268 // if user asked for help, skip missing checks
269 if (get<bool>("help")) return true;
270
271 // required named
272 for (auto &o : opts) {
273 if (o.takes_val && o.required && !vals.count(o.l)) {
274 errs.push_back("missing required --" + o.l);
275 return false;
276 }
277 }
278 // positional count
279 if (raw_pos.size() < positionals.size()) {
280 errs.push_back("missing positional <" + positionals[raw_pos.size()].l + ">");
281 return false;
282 }
283 if (raw_pos.size() > positionals.size()) {
284 errs.push_back("unexpected argument '" + raw_pos[positionals.size()] + "'");
285 return false;
286 }
287 // store positional (with type‐check)
288 for (size_t i = 0; i < positionals.size(); ++i) {
289 if (!store(positionals[i], positionals[i].l, raw_pos[i]))
290 return false;
291 }
292
293 return true;
294 }
295
296 // — single get<T> for named, plus help()/errors()/errorMsg() —
304 template<typename T>
305 T get(const std::string &name) const {
306 if constexpr(std::is_same_v<T,bool>) {
307 auto f = flags.find(name);
308 return (f != flags.end()) && f->second;
309 }
310 auto v = vals.find(name);
311 if (v == vals.end())
312 throw std::runtime_error("no value for " + name);
313 const auto &s = v->second;
314 if constexpr(std::is_same_v<T,std::string>) {
315 return s;
316 }
317 else if constexpr(std::is_integral_v<T>) {
318 if constexpr(std::is_signed_v<T>) return static_cast<T>(std::stoll(s));
319 else return static_cast<T>(std::stoull(s));
320 }
321 else if constexpr(std::is_floating_point_v<T>) {
322 return static_cast<T>(std::stod(s));
323 }
324 else static_assert(!sizeof(T), "unsupported get<T>");
325 }
326
332 bool help() const { return get<bool>("help"); }
333
339 bool errors() const { return !errs.empty(); }
340
346 std::string errorMsg() const {
347 return errs.empty() ? "" : std::string("error: ") + errs[0];
348 }
349
350 // — get positional by index —
357 template<typename T>
358 T getPositional(size_t idx) const {
359 if (idx >= positionals.size())
360 throw std::out_of_range("no positional at index " + std::to_string(idx));
361 return get<T>(positionals[idx].l);
362 }
363
364 // — pretty‐print help —
370 void printHelp(std::ostream &os = std::cout) const {
371 os << "Usage: " << prog << " [options]";
372 for (auto &p : positionals)
373 os << " <" << p.l << ">";
374 os << "\n\nOptions:\n";
375
376 // calc width
377 size_t W = 0;
378 for (auto &o : opts) {
379 std::ostringstream t;
380 t << " "
381 << (o.s ? "-" + std::string(1,o.s) + ", " : " ")
382 << "--" << o.l
383 << (o.takes_val ? "<"+o.type_name+">" : "");
384 W = std::max(W, t.str().size());
385 }
386
387 // named
388 for (auto &o : opts) {
389 std::ostringstream nm;
390 nm << " "
391 << (o.s? "-" + std::string(1,o.s)+", " : " ")
392 << "--" << o.l
393 << (o.takes_val? "<"+o.type_name+">" : "");
394 os << nm.str()
395 << std::string(W - nm.str().size() + 2, ' ')
396 << o.desc;
397 if (o.takes_val && o.required) os << " (required)";
398 if (!o.choices.empty()) {
399 os << " {";
400 for (size_t i=0; i<o.choices.size(); ++i) {
401 os << o.choices[i]
402 << (i+1<o.choices.size()? ", " : "");
403 }
404 os << "}";
405 }
406 if (!o.default_str.empty() && !o.required)
407 os << " (default: " << o.default_str << ")";
408 os << "\n";
409 }
410
411 // positionals
412 if (!positionals.empty()) {
413 os << "\nPositional arguments:\n";
414 for (auto &p : positionals) {
415 std::ostringstream nm;
416 nm << " " << p.l << "<" << p.type_name << ">";
417 os << nm.str()
418 << std::string(W + 2 - nm.str().size(), ' ')
419 << p.desc << " (required)\n";
420 }
421 }
422 }
423
424private:
425 // store + full type‐check
426 bool store(const Opt &o, const std::string &key, const std::string &val) {
427 if (o.type_name == "int") {
428 try { std::stoll(val); }
429 catch(...) {
430 errs.push_back("invalid integer for --" + key + ": '" + val + "'");
431 return false;
432 }
433 }
434 else if (o.type_name == "float") {
435 try { std::stod(val); }
436 catch(...) {
437 errs.push_back("invalid float for --" + key + ": '" + val + "'");
438 return false;
439 }
440 }
441 else if (o.type_name == "enum") {
442 if (std::find(o.choices.begin(), o.choices.end(), val) == o.choices.end()) {
443 errs.push_back("invalid value for --" + key + ": '" + val + "'");
444 return false;
445 }
446 }
447 // string always OK
448 vals[key] = val;
449 return true;
450 }
451};
An option (named or positional).
Definition simple_arg.hpp:23
bool required
Whether the option is required (true) or optional (false).
Definition simple_arg.hpp:58
std::string default_str
Default value as string (empty if none).
Definition simple_arg.hpp:43
std::string desc
Description of the option.
Definition simple_arg.hpp:38
bool takes_val
Whether the option takes a value (true) or is a flag (false).
Definition simple_arg.hpp:53
std::string type_name
Type name (e.g. "int", "float", "string", "enum", "flag").
Definition simple_arg.hpp:48
char s
Short name (e.g. 'h' for -h).
Definition simple_arg.hpp:28
std::string l
Long name (e.g. "help" for –help).
Definition simple_arg.hpp:33
std::vector< std::string > choices
Allowed choices (for enum types).
Definition simple_arg.hpp:63
void printHelp(std::ostream &os=std::cout) const
Print the help message.
Definition simple_arg.hpp:370
std::map< std::string, bool > flags
The parsed flags.
Definition simple_arg.hpp:94
std::vector< Opt > opts
The list of named options.
Definition simple_arg.hpp:76
std::string errorMsg() const
Get the error message.
Definition simple_arg.hpp:346
void add(char s, std::string l, std::string d, bool req=false, T def=T{}, std::vector< std::string > choices={})
Add a named argument.
Definition simple_arg.hpp:124
bool parse(int argc, char **argv)
Parse the command-line arguments.
Definition simple_arg.hpp:196
SimpleArg(std::string p)
Construct a new Simple Arg object.
Definition simple_arg.hpp:107
void addPositional(std::string l, std::string d, std::vector< std::string > choices={})
Add a positional argument.
Definition simple_arg.hpp:168
bool errors() const
Check if there were any errors during parsing.
Definition simple_arg.hpp:339
std::string prog
The name of the program (usually argv[0]).
Definition simple_arg.hpp:70
bool help() const
Check if help was requested.
Definition simple_arg.hpp:332
std::vector< Opt > positionals
The list of positional options.
Definition simple_arg.hpp:82
T getPositional(size_t idx) const
Get the Positional object.
Definition simple_arg.hpp:358
T get(const std::string &name) const
Get the value of a named argument.
Definition simple_arg.hpp:305
std::map< std::string, std::string > vals
The parsed values.
Definition simple_arg.hpp:88
std::vector< std::string > errs
The list of errors encountered during parsing.
Definition simple_arg.hpp:100