winchecksec
Loading...
Searching...
No Matches
argh.h
Go to the documentation of this file.
1// Copyright (c) 2016, Adi Shavit
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// * Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9// * Redistributions in binary form must reproduce the above copyright
10// notice, this list of conditions and the following disclaimer in the
11// documentation and/or other materials provided with the distribution.
12// * Neither the name of nor the names of its contributors may be used to
13// endorse or promote products derived from this software without specific
14// prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26// POSSIBILITY OF SUCH DAMAGE.
27
28#pragma once
29
30#include <algorithm>
31#include <sstream>
32#include <limits>
33#include <string>
34#include <vector>
35#include <set>
36#include <map>
37#include <cassert>
38
39namespace argh
40{
41 // Terminology:
42 // A command line is composed of 2 types of args:
43 // 1. Positional args, i.e. free standing values
44 // 2. Options: args beginning with '-'. We identify two kinds:
45 // 2.1: Flags: boolean options => (exist ? true : false)
46 // 2.2: Parameters: a name followed by a non-option value
47
48#if !defined(__GNUC__) || (__GNUC__ >= 5)
49 using string_stream = std::istringstream;
50#else
51 // Until GCC 5, istringstream did not have a move constructor.
52 // stringstream_proxy is used instead, as a workaround.
53 class stringstream_proxy
54 {
55 public:
56 stringstream_proxy() = default;
57
58 // Construct with a value.
59 stringstream_proxy(std::string const& value) :
60 stream_(value)
61 {}
62
63 // Copy constructor.
64 stringstream_proxy(const stringstream_proxy& other) :
65 stream_(other.stream_.str())
66 {
67 stream_.setstate(other.stream_.rdstate());
68 }
69
70 void setstate(std::ios_base::iostate state) { stream_.setstate(state); }
71
72 // Stream out the value of the parameter.
73 // If the conversion was not possible, the stream will enter the fail state,
74 // and operator bool will return false.
75 template<typename T>
76 stringstream_proxy& operator >> (T& thing)
77 {
78 stream_ >> thing;
79 return *this;
80 }
81
82
83 // Get the string value.
84 std::string str() const { return stream_.str(); }
85
86 std::stringbuf* rdbuf() const { return stream_.rdbuf(); }
87
88 // Check the state of the stream.
89 // False when the most recent stream operation failed
90 explicit operator bool() const { return !!stream_; }
91
92 ~stringstream_proxy() = default;
93 private:
94 std::istringstream stream_;
95 };
96 using string_stream = stringstream_proxy;
97#endif
98
99 class parser
100 {
101 public:
107
108 parser() = default;
109
110 parser(std::initializer_list<char const* const> pre_reg_names)
111 { add_params(pre_reg_names); }
112
113 parser(const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION)
114 { parse(argv, mode); }
115
116 parser(int argc, const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION)
117 { parse(argc, argv, mode); }
118
119 void add_param(std::string const& name);
120 void add_params(std::initializer_list<char const* const> init_list);
121
122 void parse(const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION);
123 void parse(int argc, const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION);
124
125 std::multiset<std::string> const& flags() const { return flags_; }
126 std::map<std::string, std::string> const& params() const { return params_; }
127 std::vector<std::string> const& pos_args() const { return pos_args_; }
128
129 // begin() and end() for using range-for over positional args.
130 std::vector<std::string>::const_iterator begin() const { return pos_args_.cbegin(); }
131 std::vector<std::string>::const_iterator end() const { return pos_args_.cend(); }
132 size_t size() const { return pos_args_.size(); }
133
135 // Accessors
136
137 // flag (boolean) accessors: return true if the flag appeared, otherwise false.
138 bool operator[](std::string const& name) const;
139
140 // multiple flag (boolean) accessors: return true if at least one of the flag appeared, otherwise false.
141 bool operator[](std::initializer_list<char const* const> init_list) const;
142
143 // returns positional arg string by order. Like argv[] but without the options
144 std::string const& operator[](size_t ind) const;
145
146 // returns a std::istream that can be used to convert a positional arg to a typed value.
147 string_stream operator()(size_t ind) const;
148
149 // same as above, but with a default value in case the arg is missing (index out of range).
150 template<typename T>
151 string_stream operator()(size_t ind, T&& def_val) const;
152
153 // parameter accessors, give a name get an std::istream that can be used to convert to a typed value.
154 // call .str() on result to get as string
155 string_stream operator()(std::string const& name) const;
156
157 // accessor for a parameter with multiple names, give a list of names, get an std::istream that can be used to convert to a typed value.
158 // call .str() on result to get as string
159 // returns the first value in the list to be found.
160 string_stream operator()(std::initializer_list<char const* const> init_list) const;
161
162 // same as above, but with a default value in case the param was missing.
163 // Non-string def_val types must have an operator<<() (output stream operator)
164 // If T only has an input stream operator, pass the string version of the type as in "3" instead of 3.
165 template<typename T>
166 string_stream operator()(std::string const& name, T&& def_val) const;
167
168 // same as above but for a list of names. returns the first value to be found.
169 template<typename T>
170 string_stream operator()(std::initializer_list<char const* const> init_list, T&& def_val) const;
171
172 private:
173 string_stream bad_stream() const;
174 std::string trim_leading_dashes(std::string const& name) const;
175 bool is_number(std::string const& arg) const;
176 bool is_option(std::string const& arg) const;
177 bool got_flag(std::string const& name) const;
178 bool is_param(std::string const& name) const;
179
180 private:
181 std::vector<std::string> args_;
182 std::map<std::string, std::string> params_;
183 std::vector<std::string> pos_args_;
184 std::multiset<std::string> flags_;
185 std::set<std::string> registeredParams_;
186 std::string empty_;
187 };
188
189
191
192 inline void parser::parse(const char * const argv[], int mode)
193 {
194 int argc = 0;
195 for (auto argvp = argv; *argvp; ++argc, ++argvp);
196 parse(argc, argv, mode);
197 }
198
200
201 inline void parser::parse(int argc, const char* const argv[], int mode /*= PREFER_FLAG_FOR_UNREG_OPTION*/)
202 {
203 // convert to strings
204 args_.resize(static_cast<decltype(args_)::size_type>(argc));
205 std::transform(argv, argv + argc, args_.begin(), [](const char* const arg) { return arg; });
206
207 // parse line
208 for (auto i = 0u; i < args_.size(); ++i)
209 {
210 if (!is_option(args_[i]))
211 {
212 pos_args_.emplace_back(args_[i]);
213 continue;
214 }
215
216 auto name = trim_leading_dashes(args_[i]);
217
218 if (!(mode & NO_SPLIT_ON_EQUALSIGN))
219 {
220 auto equalPos = name.find('=');
221 if (equalPos != std::string::npos)
222 {
223 params_.insert({ name.substr(0, equalPos), name.substr(equalPos + 1) });
224 continue;
225 }
226 }
227
228 // if the option is unregistered and should be a multi-flag
229 if (1 == (args_[i].size() - name.size()) && // single dash
230 argh::parser::SINGLE_DASH_IS_MULTIFLAG & mode && // multi-flag mode
231 !is_param(name)) // unregistered
232 {
233 std::string keep_param;
234
235 if (!name.empty() && is_param(std::string(1ul, name.back()))) // last char is param
236 {
237 keep_param += name.back();
238 name.resize(name.size() - 1);
239 }
240
241 for (auto const& c : name)
242 {
243 flags_.emplace(std::string{ c });
244 }
245
246 if (!keep_param.empty())
247 {
248 name = keep_param;
249 }
250 else
251 {
252 continue; // do not consider other options for this arg
253 }
254 }
255
256 // any potential option will get as its value the next arg, unless that arg is an option too
257 // in that case it will be determined a flag.
258 if (i == args_.size() - 1 || is_option(args_[i + 1]))
259 {
260 flags_.emplace(name);
261 continue;
262 }
263
264 // if 'name' is a pre-registered option, then the next arg cannot be a free parameter to it is skipped
265 // otherwise we have 2 modes:
266 // PREFER_FLAG_FOR_UNREG_OPTION: a non-registered 'name' is determined a flag.
267 // The following value (the next arg) will be a free parameter.
268 //
269 // PREFER_PARAM_FOR_UNREG_OPTION: a non-registered 'name' is determined a parameter, the next arg
270 // will be the value of that option.
271
274
275 bool preferParam = mode & argh::parser::PREFER_PARAM_FOR_UNREG_OPTION;
276
277 if (is_param(name) || preferParam)
278 {
279 params_.insert({ name, args_[i + 1] });
280 ++i; // skip next value, it is not a free parameter
281 continue;
282 }
283 else
284 {
285 flags_.emplace(name);
286 }
287 };
288 }
289
291
292 inline string_stream parser::bad_stream() const
293 {
294 string_stream bad;
295 bad.setstate(std::ios_base::failbit);
296 return bad;
297 }
298
300
301 inline bool parser::is_number(std::string const& arg) const
302 {
303 // inefficient but simple way to determine if a string is a number (which can start with a '-')
304 std::istringstream istr(arg);
305 double number;
306 istr >> number;
307 return !(istr.fail() || istr.bad());
308 }
309
311
312 inline bool parser::is_option(std::string const& arg) const
313 {
314 assert(0 != arg.size());
315 if (is_number(arg))
316 return false;
317 return '-' == arg[0];
318 }
319
321
322 inline std::string parser::trim_leading_dashes(std::string const& name) const
323 {
324 auto pos = name.find_first_not_of('-');
325 return std::string::npos != pos ? name.substr(pos) : name;
326 }
327
329
330 inline bool argh::parser::got_flag(std::string const& name) const
331 {
332 return flags_.end() != flags_.find(trim_leading_dashes(name));
333 }
334
336
337 inline bool argh::parser::is_param(std::string const& name) const
338 {
339 return registeredParams_.count(name);
340 }
341
343
344 inline bool parser::operator[](std::string const& name) const
345 {
346 return got_flag(name);
347 }
348
350
351 inline bool parser::operator[](std::initializer_list<char const* const> init_list) const
352 {
353 return std::any_of(init_list.begin(), init_list.end(), [&](char const* const name) { return got_flag(name); });
354 }
355
357
358 inline std::string const& parser::operator[](size_t ind) const
359 {
360 if (ind < pos_args_.size())
361 return pos_args_[ind];
362 return empty_;
363 }
364
366
367 inline string_stream parser::operator()(std::string const& name) const
368 {
369 auto optIt = params_.find(trim_leading_dashes(name));
370 if (params_.end() != optIt)
371 return string_stream(optIt->second);
372 return bad_stream();
373 }
374
376
377 inline string_stream parser::operator()(std::initializer_list<char const* const> init_list) const
378 {
379 for (auto& name : init_list)
380 {
381 auto optIt = params_.find(trim_leading_dashes(name));
382 if (params_.end() != optIt)
383 return string_stream(optIt->second);
384 }
385 return bad_stream();
386 }
387
389
390 template<typename T>
391 string_stream parser::operator()(std::string const& name, T&& def_val) const
392 {
393 auto optIt = params_.find(trim_leading_dashes(name));
394 if (params_.end() != optIt)
395 return string_stream(optIt->second);
396
397 std::ostringstream ostr;
398 ostr.precision(std::numeric_limits<long double>::max_digits10);
399 ostr << def_val;
400 return string_stream(ostr.str()); // use default
401 }
402
404
405 // same as above but for a list of names. returns the first value to be found.
406 template<typename T>
407 string_stream parser::operator()(std::initializer_list<char const* const> init_list, T&& def_val) const
408 {
409 for (auto& name : init_list)
410 {
411 auto optIt = params_.find(trim_leading_dashes(name));
412 if (params_.end() != optIt)
413 return string_stream(optIt->second);
414 }
415 std::ostringstream ostr;
416 ostr.precision(std::numeric_limits<long double>::max_digits10);
417 ostr << def_val;
418 return string_stream(ostr.str()); // use default
419 }
420
422
423 inline string_stream parser::operator()(size_t ind) const
424 {
425 if (pos_args_.size() <= ind)
426 return bad_stream();
427
428 return string_stream(pos_args_[ind]);
429 }
430
432
433 template<typename T>
434 string_stream parser::operator()(size_t ind, T&& def_val) const
435 {
436 if (pos_args_.size() <= ind)
437 {
438 std::ostringstream ostr;
439 ostr.precision(std::numeric_limits<long double>::max_digits10);
440 ostr << def_val;
441 return string_stream(ostr.str());
442 }
443
444 return string_stream(pos_args_[ind]);
445 }
446
448
449 inline void parser::add_param(std::string const& name)
450 {
451 registeredParams_.insert(trim_leading_dashes(name));
452 }
453
455
456 inline void parser::add_params(std::initializer_list<char const* const> init_list)
457 {
458 for (auto& name : init_list)
459 registeredParams_.insert(trim_leading_dashes(name));
460 }
461}
Definition argh.h:100
string_stream operator()(size_t ind) const
Definition argh.h:423
void add_params(std::initializer_list< char const *const > init_list)
Definition argh.h:456
std::map< std::string, std::string > const & params() const
Definition argh.h:126
std::vector< std::string > const & pos_args() const
Definition argh.h:127
std::vector< std::string >::const_iterator end() const
Definition argh.h:131
parser(const char *const argv[], int mode=PREFER_FLAG_FOR_UNREG_OPTION)
Definition argh.h:113
bool operator[](std::string const &name) const
Definition argh.h:344
parser()=default
void parse(const char *const argv[], int mode=PREFER_FLAG_FOR_UNREG_OPTION)
Definition argh.h:192
Mode
Definition argh.h:102
@ PREFER_PARAM_FOR_UNREG_OPTION
Definition argh.h:103
@ SINGLE_DASH_IS_MULTIFLAG
Definition argh.h:105
@ NO_SPLIT_ON_EQUALSIGN
Definition argh.h:104
@ PREFER_FLAG_FOR_UNREG_OPTION
Definition argh.h:102
parser(int argc, const char *const argv[], int mode=PREFER_FLAG_FOR_UNREG_OPTION)
Definition argh.h:116
void add_param(std::string const &name)
Definition argh.h:449
parser(std::initializer_list< char const *const > pre_reg_names)
Definition argh.h:110
size_t size() const
Definition argh.h:132
std::vector< std::string >::const_iterator begin() const
Definition argh.h:130
std::multiset< std::string > const & flags() const
Definition argh.h:125
Definition argh.h:40
std::istringstream string_stream
Definition argh.h:49