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 
15 namespace boost { namespace program_options {
16 
17     using namespace std;
18 
19 
20 #ifndef BOOST_NO_STD_WSTRING
21     namespace
22     {
convert_value(const std::wstring & s)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>::
parse(boost::any & value_store,const std::vector<std::string> & new_tokens,bool utf8) const37     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>::
parse(boost::any & value_store,const std::vector<std::string> & new_tokens,bool utf8) const63     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
name() const88     untyped_value::name() const
89     {
90         return arg;
91     }
92 
93     unsigned
min_tokens() const94     untyped_value::min_tokens() const
95     {
96         if (m_zero_tokens)
97             return 0;
98         else
99             return 1;
100     }
101 
102     unsigned
max_tokens() const103     untyped_value::max_tokens() const
104     {
105         if (m_zero_tokens)
106             return 0;
107         else
108             return 1;
109     }
110 
111 
112     void
xparse(boost::any & value_store,const std::vector<std::string> & new_tokens) const113     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>*
bool_switch()125     bool_switch()
126     {
127         return bool_switch(0);
128     }
129 
130     BOOST_PROGRAM_OPTIONS_DECL typed_value<bool>*
bool_switch(bool * v)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     */
validate(any & v,const vector<string> & xs,bool *,int)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
validate(any & v,const vector<wstring> & xs,bool *,int)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
validate(any & v,const vector<string> & xs,std::string *,int)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
validate(any & v,const vector<wstring> & xs,std::string *,int)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
check_first_occurrence(const boost::any & value)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::
invalid_option_value(const std::string & bad_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::
invalid_option_value(const std::wstring & bad_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::
invalid_bool_value(const std::string & bad_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 
error_with_option_name(const std::string & template_,const std::string & option_name,const std::string & original_token,int option_style)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 
what() const261     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 
replace_token(const string & from,const string & to) const269     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 
get_canonical_option_prefix() const281     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 
get_canonical_option_name() const302     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 
substitute_placeholders(const string & error_template) const324     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 
substitute_placeholders(const string & original_error_template) const355     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
get_template(kind_t kind)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