Bond
 
Loading...
Searching...
No Matches
cmdargs.h
1// Copyright (c) Microsoft. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
4#pragma once
5
6#include <bond/core/config.h>
7
8#include <boost/algorithm/string.hpp>
9#include <boost/lexical_cast.hpp>
10#include <boost/tokenizer.hpp>
11
12#include <iostream>
13
14namespace bond
15{
16namespace cmd
17{
18namespace detail
19{
20
21// Enum types already have generated overload for ToString.
22// For other types we use boost::lexical_cast.
23template <typename T>
24std::string ToString(const T& value)
25{
26 return boost::lexical_cast<std::string>(value);
27}
28
29// lexical_cast treats uint8_t and int8_t as characters, not numbers
30std::string ToString(const uint8_t& value)
31{
32 return boost::lexical_cast<std::string>(static_cast<int>(value));
33}
34
35std::string ToString(const int8_t& value)
36{
37 return boost::lexical_cast<std::string>(static_cast<int>(value));
38}
39
40class Metadata : boost::noncopyable
41{
42public:
43 Metadata(const bond::Metadata& metadata)
44 : metadata(metadata)
45 {}
46
47 std::string Flag() const
48 {
49 std::string flag(metadata.name);
50 std::replace(flag.begin(), flag.end(), '_', '-');
51 return "--" + flag;
52 }
53
54 std::string Param() const
55 {
56 if (IsNaked())
57 return boost::to_upper_copy(metadata.name);
58 else
59 return Flag() + "=" + boost::to_upper_copy(metadata.name);
60 }
61
62 std::string Abbr() const
63 {
64 if (HasAttribute("abbr"))
65 return "-" + Attribute("abbr");
66 else
67 return "";
68 }
69
70 template <typename T>
71 std::string Help() const
72 {
73 std::string help;
74
75 if (HasAttribute("help"))
76 help = Attribute("help");
77 else
78 help = HelpFromType<T>();
79
80 std::string default_value = Default<T>();
81
82 if (!default_value.empty())
83 {
84 if (!help.empty())
85 help += ", ";
86
87 help += "default " + default_value;
88 }
89
90 return help;
91 }
92
93 std::string Help() const
94 {
95 return Attribute("help");
96 }
97
98 bool IsNaked() const
99 {
100 return HasAttribute("naked");
101 }
102
103 bool IsOptional() const
104 {
105 return metadata.modifier == bond::Optional;
106 }
107
108private:
109 bool HasAttribute(const std::string& attr) const
110 {
111 return metadata.attributes.end() != metadata.attributes.find(attr);
112 }
113
114 std::string Attribute(const std::string& attr) const
115 {
116 std::map<std::string, std::string>::const_iterator it;
117
118 it = metadata.attributes.find(attr);
119
120 if (it != metadata.attributes.end())
121 return it->second;
122 else
123 return "";
124 }
125
126 template <typename T>
127 typename boost::disable_if_c<bond::is_list_container<T>::value
128 || std::is_enum<T>::value, std::string>::type
129 HelpFromType() const
130 {
131 return "";
132 }
133
134 template <typename T>
135 typename boost::enable_if<std::is_enum<T>, std::string>::type
136 HelpFromType() const
137 {
138 std::string enums;
139
140 const std::map<std::string, T>& names = bond::GetEnumNames<T>();
141
142 for (typename std::map<std::string, T>::const_iterator it = names.begin(); it != names.end(); ++it)
143 {
144 if (!enums.empty())
145 enums += " | ";
146 enums += it->first;
147 }
148 return enums;
149 }
150
151 template <typename T>
152 typename boost::enable_if<bond::is_list_container<T>, std::string>::type
153 HelpFromType() const
154 {
155 std::string help = HelpFromType<typename bond::element_type<T>::type>();
156
157 if (!help.empty())
158 help = "comma-separated list of: " + help;
159
160 return help;
161 }
162
163 template <typename T>
164 typename boost::disable_if<bond::is_basic_type<T>, std::string>::type
165 Default() const
166 {
167 return "";
168 }
169
170 template <typename T>
171 typename boost::enable_if<bond::is_basic_type<T>, std::string>::type
172 Default() const
173 {
174 if (metadata.modifier == bond::Optional && !metadata.default_value.nothing)
175 {
176 T var;
177
178 bond::detail::VariantGet(metadata.default_value, var);
179
180 return ToString(var);
181 }
182 else
183 return "";
184 }
185
186 const bond::Metadata& metadata;
187};
188
189
190// Options
191class Options
192{
193public:
194 // ctor
195 Options(int argc, char** argv)
196 {
197 for (int i = 1; i < argc; ++i)
198 {
199 std::string value = argv[i];
200 std::string name;
201
202 if (value[0] == '-' && value != "-" && value != "--")
203 {
204 size_t pos = value.find_first_of("=:");
205
206 name = value.substr(0, pos);
207
208 // unpack multiple abbreviated options in single param, e.g.:
209 // -fxd -> -f -x -d
210 if (name[1] != '-')
211 while(name.length() > 2)
212 {
213 params.push_back(Param(name.substr(0, 2), ""));
214 name.erase(1, 1);
215 }
216
217 if (pos != std::string::npos)
218 value = value.substr(pos + 1);
219 else
220 value = "";
221 }
222
223 params.push_back(Param(name, value));
224 }
225 }
226
227 // GetFlag
228 bool GetFlag(const std::string& flag, const std::string& abbr)
229 {
230 Params::iterator it = FindParam(flag, abbr);
231
232 if (it != params.end())
233 {
234 if (!it->value.empty())
235 throw std::runtime_error("Invalid parameter(s):\n " + it->value);
236
237 return true;
238 }
239
240 return false;
241 }
242
243 // GetParam
244 bool GetParam(std::string& value)
245 {
246 return GetParam("", "", value);
247 }
248
249 // GetParam
250 bool GetParam(const std::string& flag, const std::string& abbr, std::string& value)
251 {
252 Params::iterator it = FindParam(flag, abbr);
253
254 if (it != params.end())
255 {
256 value = it->value;
257
258 if(value.empty() && ++it != params.end() && !it->used && it->name.empty())
259 {
260 value = it->value;
261 it->used = true;
262 }
263
264 return true;
265 }
266
267 return false;
268 }
269
270 // Leftovers
271 std::string Leftovers()
272 {
273 std::string leftovers;
274
275 for (Params::iterator it = params.begin(); it != params.end(); ++it)
276 if (!it->used)
277 {
278 leftovers += " " + it->name;
279
280 if (!it->name.empty() && !it->value.empty())
281 leftovers += "=";
282
283 leftovers += it->value + "\n";
284 }
285
286 return leftovers;
287 }
288
289private:
290 struct Param
291 {
292 Param(const std::string& name, const std::string& value)
293 : name(name),
294 value(value),
295 used(false)
296 {}
297
298 std::string name;
299 std::string value;
300 bool used;
301 };
302
303 typedef std::list<Param> Params;
304
305 Params::iterator FindParam(const std::string& flag, const std::string& abbr)
306 {
307 Params::iterator it;
308
309 for (it = params.begin(); it != params.end(); ++it)
310 if (!it->used && (it->name == flag || (it->name == abbr && !abbr.empty())))
311 {
312 it->used = true;
313 break;
314 }
315
316 return it;
317 }
318
319 Params params;
320};
321
322
323// CmdArg
324class CmdArg
326{
327public:
328 CmdArg(int argc, char** argv, bool partial = false)
329 : options(boost::make_shared<Options>(argc, argv)),
330 partial(partial)
331 {}
332
333 CmdArg(const boost::shared_ptr<Options>& options, bool partial = false)
334 : options(options),
335 partial(partial)
336 {}
337
338 void Begin(const detail::Metadata& /*metadata*/) const
339 {}
340
341 void End() const
342 {
343 if (!partial)
344 {
345 std::string leftovers = options->Leftovers();
346
347 if (!leftovers.empty())
348 throw std::runtime_error("Invalid parameter(s):\n" + leftovers);
349 }
350 }
351
352 template <typename T>
353 bool Base(T& value) const
354 {
355 return bond::Apply(CmdArg(options, true), value);
356 }
357
358 template <typename T>
359 typename boost::enable_if<bond::is_list_container<T>, bool>::type
360 Field(uint16_t /*id*/, const detail::Metadata& metadata, T& var) const
361 {
362 while(GetParam(metadata, var));
363 return false;
364 }
365
366 template <typename T>
367 typename boost::disable_if<bond::is_list_container<T>, bool>::type
368 Field(uint16_t /*id*/, const detail::Metadata& metadata, T& var) const
369 {
370 GetParam(metadata, var);
371 return false;
372 }
373
374private:
375 // Parse enum
376 template <typename T>
377 typename boost::enable_if<std::is_enum<T> >::type
378 Parse(const std::string& value, T& var) const
379 {
380 if (!ToEnum(var, value.c_str()))
381 throw std::runtime_error("Invalid parameter(s):\n " + value);
382 }
383
384 // Parse number
385 template <typename T>
386 typename boost::enable_if<std::is_arithmetic<T> >::type
387 Parse(const std::string& value, T& var) const
388 {
389 try
390 {
391 var = boost::lexical_cast<T>(value);
392 }
393 catch(const std::exception&)
394 {
395 throw std::runtime_error("Invalid parameter(s):\n " + value);
396 }
397 }
398
399 // Parse string
400 void Parse(const std::string& value, std::string& var) const
401 {
402 var = value;
403 }
404
405 // Parse list
406 template <typename T>
407 typename boost::enable_if<bond::is_list_container<T> >::type
408 Parse(const std::string& value, T& var) const
409 {
410 typedef boost::tokenizer<boost::escaped_list_separator<char> > tokenizer;
411
412 tokenizer tok(value);
413
414 for(tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
415 {
416 typename bond::element_type<T>::type tmp;
417
418 Parse(*it, tmp);
419 var.push_back(tmp);
420 }
421 }
422
423 template <typename T>
424 void Parse(const std::string& value, maybe<T>& var) const
425 {
426 Parse(value, var.set_value());
427 }
428
429
430 // bool param
431 bool GetParam(const detail::Metadata& metadata, bool& var) const
432 {
433 // required or naked bool flags don't make sense
434 BOOST_ASSERT(metadata.IsOptional());
435 BOOST_ASSERT(!metadata.IsNaked());
436
437 return (var = options->GetFlag(metadata.Flag(), metadata.Abbr()));
438 }
439
440
441 // generic param
442 template <typename T>
443 bool GetParam(const detail::Metadata& metadata, T& var) const
444 {
445 std::string value;
446
447 if (metadata.IsNaked())
448 options->GetParam(value);
449 else
450 options->GetParam(metadata.Flag(), metadata.Abbr(), value);
451
452 if (value.empty())
453 {
454 if(!metadata.IsOptional())
455 throw std::runtime_error("Required parameter " + metadata.Param() + " missing.");
456 else
457 return false;
458 }
459
460 Parse(value, var);
461
462 return true;
463 }
464
465 boost::shared_ptr<Options> options;
466 bool partial;
467};
468
469
470class Usage
472{
473public:
474 Usage(const char* program, std::ostream& out = std::cerr)
475 : program(program),
476 out(out)
477 {}
478
479 void Begin(const detail::Metadata& metadata) const
480 {
481 std::string help = metadata.Help();
482
483 if (!help.empty())
484 out << std::endl << "Usage: " << program << " " << help << std::endl << std::endl;
485 }
486
487 void End() const
488 {}
489
490 template <typename T>
491 bool Base(const T& value) const
492 {
493 bond::Apply(*this, value);
494 return false;
495 }
496
497 bool Field(uint16_t /*id*/, const detail::Metadata& metadata, const bool& /*value*/) const
498 {
499 Print(metadata.Flag(), metadata.Abbr(), metadata.Help());
500 return false;
501 }
502
503 template <typename T>
504 bool Field(uint16_t /*id*/, const detail::Metadata& metadata, const bond::maybe<T>& /*value*/) const
505 {
506 std::string help = metadata.Help<T>();
507
508 Print(metadata.Param(), metadata.Abbr(), help);
509 return false;
510 }
511
512 template <typename T>
513 bool Field(uint16_t /*id*/, const detail::Metadata& metadata, const T& /*value*/) const
514 {
515 std::string help = metadata.Help<T>();
516
517 Print(metadata.Param(), metadata.Abbr(), help);
518 return false;
519 }
520
521protected:
522 void Print(const std::string& arg, const std::string& abbr, const std::string& help) const
523 {
524 int indent = (std::max)(30, static_cast<int>(arg.size()) + 5);
525 std::string formated = FormatHelp(help, indent);
526 out << " ";
527 out.width(2);
528 out << abbr << " ";
529 out.setf(std::ios::left, std::ios::adjustfield);
530 out.width(indent - 4);
531 out << arg << formated << std::endl;
532 }
533
534 std::string FormatHelp(const std::string& help, int indent) const
535 {
536 std::string formated = help;
537 int i = 79 - indent;
538
539 for (int begin = 0; i < static_cast<int>(formated.length()); begin += 80, i = begin + 79 - indent)
540 {
541 while (i >= begin && !isspace(static_cast<int>(formated[i])))
542 --i;
543
544 while (i < static_cast<int>(formated.length()) && isspace(static_cast<int>(formated[i])))
545 ++i;
546
547 formated.insert(i, begin + 80 - i, ' ');
548 formated[i + begin + 79 - i - indent] = '\n';
549 }
550
551 return formated;
552 }
553
554 std::string program;
555 std::ostream& out;
556};
557
558} // namespace detail
559} // namespace cmd
560} // namespace bond
namespace bond
Definition: apply.h:17
Base class for transforms which modify a Bond type instance.
Definition: tags.h:77
Base class for serializing transforms.
Definition: tags.h:61