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