Skip to content

Commit ca288df

Browse files
authored
Merge f1cc1b8 into 0beed2c
2 parents 0beed2c + f1cc1b8 commit ca288df

File tree

5 files changed

+444
-22
lines changed

5 files changed

+444
-22
lines changed

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
[submodule "deps"]
55
path = deps
66
url = ../deps
7-
shallow = true
7+
shallow = true

src/dbg/args.h

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
#pragma once
2+
3+
#include <cstdlib>
4+
#include <string>
5+
#include <vector>
6+
#include <functional>
7+
#include <unordered_set>
8+
#include <stdexcept>
9+
10+
// https://github.com/LLVMParty/args
11+
class ArgumentParser
12+
{
13+
protected:
14+
void addPositional(const std::string & name, std::string & value, const std::string & help, bool required = false)
15+
{
16+
auto fn = [this, &value]()
17+
{
18+
value = arg;
19+
};
20+
positionalArgs.push_back(Arg{ name, help, required, fn });
21+
}
22+
23+
void addString(const std::string & flagname, std::string & value, const std::string & help, bool required = false)
24+
{
25+
auto fn = [this, flagname, &value]
26+
{
27+
if(arg.substr(0, flagname.length()) == flagname)
28+
{
29+
if(arg.length() == flagname.length())
30+
{
31+
// -flagname <value>
32+
if(i + 1 >= argc)
33+
{
34+
throw std::runtime_error("missing value for '" + flagname + "' argument");
35+
}
36+
value = argv[++i];
37+
if(value.empty())
38+
{
39+
throw std::runtime_error("empty value for '" + flagname + "' argument");
40+
}
41+
markExtracted(flagname);
42+
}
43+
else if(arg[flagname.length()] == '=')
44+
{
45+
// -flagname=<value>
46+
value = arg.substr(flagname.length() + 1);
47+
markExtracted(flagname);
48+
}
49+
}
50+
};
51+
flagArgs.push_back(Arg{ flagname, help, required, fn });
52+
}
53+
54+
void addBool(const std::string & flagname, bool & value, const std::string & help, bool required = false)
55+
{
56+
auto fn = [this, flagname, &value]
57+
{
58+
if(arg.substr(0, flagname.length()) == flagname)
59+
{
60+
if(arg.length() == flagname.length())
61+
{
62+
// -flagname
63+
value = true;
64+
markExtracted(flagname);
65+
}
66+
else if(arg[flagname.length()] == '=')
67+
{
68+
// -flagname=<value>
69+
auto strValue = arg.substr(flagname.length() + 1);
70+
if(strValue.empty())
71+
{
72+
throw std::runtime_error("empty value for '" + flagname + "' argument");
73+
}
74+
value = strValue == "1" || strValue == "true";
75+
markExtracted(flagname);
76+
}
77+
}
78+
};
79+
flagArgs.push_back(Arg{ flagname, help, required, fn });
80+
}
81+
82+
explicit ArgumentParser(std::string description) : description(std::move(description))
83+
{
84+
}
85+
86+
public:
87+
virtual ~ArgumentParser() = default;
88+
ArgumentParser(const ArgumentParser &) = delete;
89+
ArgumentParser & operator=(const ArgumentParser &) = delete;
90+
ArgumentParser(ArgumentParser &&) = delete;
91+
ArgumentParser & operator=(ArgumentParser &&) = delete;
92+
93+
void parseOrExit(int argc, char** argv, const char* helpflag = "-help")
94+
{
95+
bool help = false;
96+
addBool(helpflag, help, "Show this message");
97+
98+
try
99+
{
100+
parse(argc, argv);
101+
}
102+
catch(const std::exception & e)
103+
{
104+
printf("Error: %s\n\nHelp:\n%s\n", e.what(), helpStr().c_str());
105+
std::exit(help ? EXIT_SUCCESS : EXIT_FAILURE);
106+
}
107+
if(help)
108+
{
109+
puts(helpStr().c_str());
110+
std::exit(EXIT_SUCCESS);
111+
}
112+
}
113+
114+
void parse(int argc, char** argv)
115+
{
116+
this->argc = argc;
117+
this->argv = argv;
118+
bool seenRequired = false;
119+
for(const auto & positionalArg : positionalArgs)
120+
{
121+
if(positionalArg.name.empty())
122+
{
123+
throw std::runtime_error("cannot add positional argument without name");
124+
}
125+
if(!positionalArg.required)
126+
{
127+
if(seenRequired)
128+
{
129+
throw std::runtime_error("cannot add required positional argument after an optional one");
130+
}
131+
}
132+
else
133+
{
134+
seenRequired = true;
135+
}
136+
}
137+
for(const auto & flagArg : flagArgs)
138+
{
139+
if(flagArg.name.empty())
140+
{
141+
throw std::runtime_error("cannot add argument without name");
142+
}
143+
if(flagArg.name[0] != '-')
144+
{
145+
throw std::runtime_error("invalid argument name '" + flagArg.name + "'");
146+
}
147+
}
148+
size_t positionalIndex = 0;
149+
for(i = 1; i < argc; i++)
150+
{
151+
arg = std::string(argv[i]);
152+
if(arg.empty())
153+
{
154+
continue;
155+
}
156+
if(arg[0] == '-')
157+
{
158+
didExtract = false;
159+
for(const auto & flag : flagArgs)
160+
{
161+
flag.fn();
162+
}
163+
if(!didExtract)
164+
{
165+
throw std::runtime_error("unknown argument '" + arg + "'");
166+
}
167+
}
168+
else
169+
{
170+
if(positionalIndex + 1 > positionalArgs.size())
171+
{
172+
throw std::runtime_error("unexpected positional argument '" + arg + "'");
173+
}
174+
const auto & positionalArg = positionalArgs[positionalIndex++];
175+
if(positionalArg.name[0] == '-')
176+
{
177+
markExtracted(positionalArg.name);
178+
}
179+
positionalArg.fn();
180+
}
181+
}
182+
for(const auto & flagArg : flagArgs)
183+
{
184+
if(!flagArg.required)
185+
{
186+
continue;
187+
}
188+
if(flagsExtracted.count(flagArg.name) == 0)
189+
{
190+
throw std::runtime_error("required argument '" + flagArg.name + "' missing");
191+
}
192+
}
193+
for(size_t i = positionalIndex; i < positionalArgs.size(); i++)
194+
{
195+
const auto & positionalArg = positionalArgs[i];
196+
if(positionalArg.required)
197+
{
198+
if(flagsExtracted.count(positionalArg.name) > 0)
199+
{
200+
continue;
201+
}
202+
throw std::runtime_error("required positional argument missing");
203+
}
204+
}
205+
}
206+
207+
std::string helpStr() const
208+
{
209+
std::string help;
210+
help += " ";
211+
help += argv[0];
212+
help += " {OPTIONS}";
213+
214+
for(const auto & positionalArg : positionalArgs)
215+
{
216+
help += " ";
217+
if(!positionalArg.required)
218+
{
219+
help += '[';
220+
}
221+
if(positionalArg.name[0] == '-')
222+
{
223+
help += "[" + positionalArg.name + "]";
224+
help += " <value>";
225+
}
226+
else
227+
{
228+
help += positionalArg.name;
229+
}
230+
if(!positionalArg.required)
231+
{
232+
help += ']';
233+
}
234+
}
235+
help += '\n';
236+
237+
if(!description.empty())
238+
{
239+
help += "\n ";
240+
help += description;
241+
help += "\n\n";
242+
}
243+
244+
help += " OPTIONS:\n";
245+
246+
size_t maxLen = 0;
247+
for(const auto & flagArg : flagArgs)
248+
{
249+
if(flagArg.name.size() > maxLen)
250+
{
251+
maxLen = flagArg.name.size();
252+
}
253+
}
254+
for(const auto & flagArg : flagArgs)
255+
{
256+
help += "\n ";
257+
help += flagArg.name;
258+
for(size_t i = 0; i < maxLen - flagArg.name.size(); i++)
259+
{
260+
help += ' ';
261+
}
262+
help += " ";
263+
help += flagArg.help;
264+
if(!flagArg.required)
265+
{
266+
help += " (optional)";
267+
}
268+
}
269+
270+
return help;
271+
}
272+
273+
private:
274+
struct Arg
275+
{
276+
std::string name;
277+
std::string help;
278+
bool required = false;
279+
std::function<void()> fn;
280+
281+
Arg() = default;
282+
Arg(std::string name, std::string help, bool required, std::function<void()> fn)
283+
: name(std::move(name))
284+
, help(std::move(help))
285+
, required(required)
286+
, fn(std::move(fn))
287+
{
288+
}
289+
};
290+
291+
std::string description;
292+
std::vector<Arg> positionalArgs;
293+
std::vector<Arg> flagArgs;
294+
295+
int i = 1;
296+
int argc = 0;
297+
char** argv = nullptr;
298+
bool didExtract = false;
299+
std::string arg;
300+
std::unordered_set<std::string> flagsExtracted;
301+
302+
void markExtracted(const std::string & flagname)
303+
{
304+
didExtract = true;
305+
if(flagsExtracted.count(flagname) > 0)
306+
{
307+
throw std::runtime_error("duplicate value for '" + flagname + "' argument");
308+
}
309+
flagsExtracted.insert(flagname);
310+
}
311+
};

0 commit comments

Comments
 (0)