1// Copyright Vladimir Prus 2004.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt
4// or copy at http://www.boost.org/LICENSE_1_0.txt)
5
6#define BOOST_PROGRAM_OPTIONS_SOURCE
7#include <boost/program_options/config.hpp>
8#include <boost/program_options/value_semantic.hpp>
9#include <boost/program_options/detail/convert.hpp>
10#include <boost/program_options/detail/cmdline.hpp>
11#include <set>
12
13#include <cctype>
14
15namespace boost { namespace program_options {
16
17 using namespace std;
18
19
20#ifndef BOOST_NO_STD_WSTRING
21 namespace
22 {
23 std::string convert_value(const std::wstring& s)
24 {
25 try {
26 return to_local_8_bit(s);
27 }
28 catch(const std::exception&) {
29 return "<unrepresentable unicode string>";
30 }
31 }
32 }
33#endif
34
35 void
36 value_semantic_codecvt_helper<char>::
37 parse(boost::any& value_store,
38 const std::vector<std::string>& new_tokens,
39 bool utf8) const
40 {
41 if (utf8) {
42#ifndef BOOST_NO_STD_WSTRING
43 // Need to convert to local encoding.
44 std::vector<string> local_tokens;
45 for (unsigned i = 0; i < new_tokens.size(); ++i) {
46 std::wstring w = from_utf8(new_tokens[i]);
47 local_tokens.push_back(to_local_8_bit(w));
48 }
49 xparse(value_store, local_tokens);
50#else
51 boost::throw_exception(
52 std::runtime_error("UTF-8 conversion not supported."));
53#endif
54 } else {
55 // Already in local encoding, pass unmodified
56 xparse(value_store, new_tokens);
57 }
58 }
59
60#ifndef BOOST_NO_STD_WSTRING
61 void
62 value_semantic_codecvt_helper<wchar_t>::
63 parse(boost::any& value_store,
64 const std::vector<std::string>& new_tokens,
65 bool utf8) const
66 {
67 std::vector<wstring> tokens;
68 if (utf8) {
69 // Convert from utf8
70 for (unsigned i = 0; i < new_tokens.size(); ++i) {
71 tokens.push_back(from_utf8(new_tokens[i]));
72 }
73
74 } else {
75 // Convert from local encoding
76 for (unsigned i = 0; i < new_tokens.size(); ++i) {
77 tokens.push_back(from_local_8_bit(new_tokens[i]));
78 }
79 }
80
81 xparse(value_store, tokens);
82 }
83#endif
84
85 BOOST_PROGRAM_OPTIONS_DECL std::string arg("arg");
86
87 std::string
88 untyped_value::name() const
89 {
90 return arg;
91 }
92
93 unsigned
94 untyped_value::min_tokens() const
95 {
96 if (m_zero_tokens)
97 return 0;
98 else
99 return 1;
100 }
101
102 unsigned
103 untyped_value::max_tokens() const
104 {
105 if (m_zero_tokens)
106 return 0;
107 else
108 return 1;
109 }
110
111
112 void
113 untyped_value::xparse(boost::any& value_store,
114 const std::vector<std::string>& new_tokens) const
115 {
116 if (!value_store.empty())
117 boost::throw_exception(
118 multiple_occurrences());
119 if (new_tokens.size() > 1)
120 boost::throw_exception(multiple_values());
121 value_store = new_tokens.empty() ? std::string("") : new_tokens.front();
122 }
123
124 BOOST_PROGRAM_OPTIONS_DECL typed_value<bool>*
125 bool_switch()
126 {
127 return bool_switch(0);
128 }
129
130 BOOST_PROGRAM_OPTIONS_DECL typed_value<bool>*
131 bool_switch(bool* v)
132 {
133 typed_value<bool>* r = new typed_value<bool>(v);
134 r->default_value(0);
135 r->zero_tokens();
136
137 return r;
138 }
139
140 /* Validates bool value.
141 Any of "1", "true", "yes", "on" will be converted to "1".<br>
142 Any of "0", "false", "no", "off" will be converted to "0".<br>
143 Case is ignored. The 'xs' vector can either be empty, in which
144 case the value is 'true', or can contain explicit value.
145 */
146 BOOST_PROGRAM_OPTIONS_DECL void validate(any& v, const vector<string>& xs,
147 bool*, int)
148 {
149 check_first_occurrence(v);
150 string s(get_single_string(xs, true));
151
152 for (size_t i = 0; i < s.size(); ++i)
153 s[i] = char(tolower(s[i]));
154
155 if (s.empty() || s == "on" || s == "yes" || s == "1" || s == "true")
156 v = any(true);
157 else if (s == "off" || s == "no" || s == "0" || s == "false")
158 v = any(false);
159 else
160 boost::throw_exception(invalid_bool_value(s));
161 }
162
163 // This is blatant copy-paste. However, templating this will cause a problem,
164 // since wstring can't be constructed/compared with char*. We'd need to
165 // create auxiliary 'widen' routine to convert from char* into
166 // needed string type, and that's more work.
167#if !defined(BOOST_NO_STD_WSTRING)
168 BOOST_PROGRAM_OPTIONS_DECL
169 void validate(any& v, const vector<wstring>& xs, bool*, int)
170 {
171 check_first_occurrence(v);
172 wstring s(get_single_string(xs, true));
173
174 for (size_t i = 0; i < s.size(); ++i)
175 s[i] = wchar_t(tolower(s[i]));
176
177 if (s.empty() || s == L"on" || s == L"yes" || s == L"1" || s == L"true")
178 v = any(true);
179 else if (s == L"off" || s == L"no" || s == L"0" || s == L"false")
180 v = any(false);
181 else
182 boost::throw_exception(invalid_bool_value(convert_value(s)));
183 }
184#endif
185 BOOST_PROGRAM_OPTIONS_DECL
186 void validate(any& v, const vector<string>& xs, std::string*, int)
187 {
188 check_first_occurrence(v);
189 v = any(get_single_string(xs));
190 }
191
192#if !defined(BOOST_NO_STD_WSTRING)
193 BOOST_PROGRAM_OPTIONS_DECL
194 void validate(any& v, const vector<wstring>& xs, std::string*, int)
195 {
196 check_first_occurrence(v);
197 v = any(get_single_string(xs));
198 }
199#endif
200
201 namespace validators {
202
203 BOOST_PROGRAM_OPTIONS_DECL
204 void check_first_occurrence(const boost::any& value)
205 {
206 if (!value.empty())
207 boost::throw_exception(
208 multiple_occurrences());
209 }
210 }
211
212
213 invalid_option_value::
214 invalid_option_value(const std::string& bad_value)
215 : validation_error(validation_error::invalid_option_value)
216 {
217 set_substitute("value", bad_value);
218 }
219
220#ifndef BOOST_NO_STD_WSTRING
221 invalid_option_value::
222 invalid_option_value(const std::wstring& bad_value)
223 : validation_error(validation_error::invalid_option_value)
224 {
225 set_substitute("value", convert_value(bad_value));
226 }
227#endif
228
229
230
231 invalid_bool_value::
232 invalid_bool_value(const std::string& bad_value)
233 : validation_error(validation_error::invalid_bool_value)
234 {
235 set_substitute("value", bad_value);
236 }
237
238
239
240
241
242
243 error_with_option_name::error_with_option_name( const std::string& template_,
244 const std::string& option_name,
245 const std::string& original_token,
246 int option_style) :
247 error(template_),
248 m_option_style(option_style),
249 m_error_template(template_)
250 {
251 // parameter | placeholder | value
252 // --------- | ----------- | -----
253 set_substitute_default("canonical_option", "option '%canonical_option%'", "option");
254 set_substitute_default("value", "argument ('%value%')", "argument");
255 set_substitute_default("prefix", "%prefix%", "");
256 m_substitutions["option"] = option_name;
257 m_substitutions["original_token"] = original_token;
258 }
259
260
261 const char* error_with_option_name::what() const throw()
262 {
263 // will substitute tokens each time what is run()
264 substitute_placeholders(m_error_template);
265
266 return m_message.c_str();
267 }
268
269 void error_with_option_name::replace_token(const string& from, const string& to) const
270 {
271 for (;;)
272 {
273 std::size_t pos = m_message.find(from.c_str(), 0, from.length());
274 // not found: all replaced
275 if (pos == std::string::npos)
276 return;
277 m_message.replace(pos, from.length(), to);
278 }
279 }
280
281 string error_with_option_name::get_canonical_option_prefix() const
282 {
283 switch (m_option_style)
284 {
285 case command_line_style::allow_dash_for_short:
286 return "-";
287 case command_line_style::allow_slash_for_short:
288 return "/";
289 case command_line_style::allow_long_disguise:
290 return "-";
291 case command_line_style::allow_long:
292 return "--";
293 case 0:
294 return "";
295 }
296 throw std::logic_error("error_with_option_name::m_option_style can only be "
297 "one of [0, allow_dash_for_short, allow_slash_for_short, "
298 "allow_long_disguise or allow_long]");
299 }
300
301
302 string error_with_option_name::get_canonical_option_name() const
303 {
304 if (!m_substitutions.find("option")->second.length())
305 return m_substitutions.find("original_token")->second;
306
307 string original_token = strip_prefixes(m_substitutions.find("original_token")->second);
308 string option_name = strip_prefixes(m_substitutions.find("option")->second);
309
310 // For long options, use option name
311 if (m_option_style == command_line_style::allow_long ||
312 m_option_style == command_line_style::allow_long_disguise)
313 return get_canonical_option_prefix() + option_name;
314
315 // For short options use first letter of original_token
316 if (m_option_style && original_token.length())
317 return get_canonical_option_prefix() + original_token[0];
318
319 // no prefix
320 return option_name;
321 }
322
323
324 void error_with_option_name::substitute_placeholders(const string& error_template) const
325 {
326 m_message = error_template;
327 std::map<std::string, std::string> substitutions(m_substitutions);
328 substitutions["canonical_option"] = get_canonical_option_name();
329 substitutions["prefix"] = get_canonical_option_prefix();
330
331
332 //
333 // replace placeholder with defaults if values are missing
334 //
335 for (map<string, string_pair>::const_iterator iter = m_substitution_defaults.begin();
336 iter != m_substitution_defaults.end(); ++iter)
337 {
338 // missing parameter: use default
339 if (substitutions.count(iter->first) == 0 ||
340 substitutions[iter->first].length() == 0)
341 replace_token(iter->second.first, iter->second.second);
342 }
343
344
345 //
346 // replace placeholder with values
347 // placeholder are denoted by surrounding '%'
348 //
349 for (map<string, string>::iterator iter = substitutions.begin();
350 iter != substitutions.end(); ++iter)
351 replace_token('%' + iter->first + '%', iter->second);
352 }
353
354
355 void ambiguous_option::substitute_placeholders(const string& original_error_template) const
356 {
357 // For short forms, all alternatives must be identical, by
358 // definition, to the specified option, so we don't need to
359 // display alternatives
360 if (m_option_style == command_line_style::allow_dash_for_short ||
361 m_option_style == command_line_style::allow_slash_for_short)
362 {
363 error_with_option_name::substitute_placeholders(original_error_template);
364 return;
365 }
366
367
368 string error_template = original_error_template;
369 // remove duplicates using std::set
370 std::set<std::string> alternatives_set (m_alternatives.begin(), m_alternatives.end());
371 std::vector<std::string> alternatives_vec (alternatives_set.begin(), alternatives_set.end());
372
373 error_template += " and matches ";
374 // Being very cautious: should be > 1 alternative!
375 if (alternatives_vec.size() > 1)
376 {
377 for (unsigned i = 0; i < alternatives_vec.size() - 1; ++i)
378 error_template += "'%prefix%" + alternatives_vec[i] + "', ";
379 error_template += "and ";
380 }
381
382 // there is a programming error if multiple options have the same name...
383 if (m_alternatives.size() > 1 && alternatives_vec.size() == 1)
384 error_template += "different versions of ";
385
386 error_template += "'%prefix%" + alternatives_vec.back() + "'";
387
388
389 // use inherited logic
390 error_with_option_name::substitute_placeholders(error_template);
391 }
392
393
394
395
396
397
398 string
399 validation_error::get_template(kind_t kind)
400 {
401 // Initially, store the message in 'const char*' variable,
402 // to avoid conversion to std::string in all cases.
403 const char* msg;
404 switch(kind)
405 {
406 case invalid_bool_value:
407 msg = "the argument ('%value%') for option '%canonical_option%' is invalid. Valid choices are 'on|off', 'yes|no', '1|0' and 'true|false'";
408 break;
409 case invalid_option_value:
410 msg = "the argument ('%value%') for option '%canonical_option%' is invalid";
411 break;
412 case multiple_values_not_allowed:
413 msg = "option '%canonical_option%' only takes a single argument";
414 break;
415 case at_least_one_value_required:
416 msg = "option '%canonical_option%' requires at least one argument";
417 break;
418 // currently unused
419 case invalid_option:
420 msg = "option '%canonical_option%' is not valid";
421 break;
422 default:
423 msg = "unknown error";
424 }
425 return msg;
426 }
427
428}}
429