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