diff --git a/include/boost/program_options/cmdline.hpp b/include/boost/program_options/cmdline.hpp index 0e8fea2..8705e60 100644 --- a/include/boost/program_options/cmdline.hpp +++ b/include/boost/program_options/cmdline.hpp @@ -62,14 +62,19 @@ namespace boost { namespace program_options { namespace command_line_style { long option name if guessing is in effect. */ allow_guessing = allow_sticky << 1, - /** Ignore the difference in case for options. - @todo Should this apply to long options only? + /** Ignore the difference in case for long options. */ - case_insensitive = allow_guessing << 1, + long_case_insensitive = allow_guessing << 1, + /** Ignore the difference in case for short options. + */ + short_case_insensitive = long_case_insensitive << 1, + /** Ignore the difference in case for all options. + */ + case_insensitive = (long_case_insensitive | short_case_insensitive), /** Allow long options with single option starting character, e.g -foo=10 */ - allow_long_disguise = case_insensitive << 1, + allow_long_disguise = short_case_insensitive << 1, /** The more-or-less traditional unix style. */ unix_style = (allow_short | short_allow_adjacent | short_allow_next | allow_long | long_allow_adjacent | long_allow_next diff --git a/include/boost/program_options/option.hpp b/include/boost/program_options/option.hpp index 635f708..557c692 100644 --- a/include/boost/program_options/option.hpp +++ b/include/boost/program_options/option.hpp @@ -23,10 +23,17 @@ namespace boost { namespace program_options { template class basic_option { public: - basic_option() : position_key(-1), unregistered(false) {} + basic_option() + : position_key(-1) + , unregistered(false) + , case_insensitive(false) + {} basic_option(const std::string& string_key, - const std::vector< std::string> &value) - : string_key(string_key), value(value), unregistered(false) + const std::vector< std::string> &value) + : string_key(string_key) + , value(value) + , unregistered(false) + , case_insensitive(false) {} /** String key of this option. Intentionally independent of the template @@ -50,7 +57,10 @@ namespace boost { namespace program_options { recovered from the "original_tokens" member. */ bool unregistered; - + /** True if string_key has to be handled + case insensitive. + */ + bool case_insensitive; }; typedef basic_option option; typedef basic_option woption; diff --git a/include/boost/program_options/options_description.hpp b/include/boost/program_options/options_description.hpp index 2770d41..0486f02 100644 --- a/include/boost/program_options/options_description.hpp +++ b/include/boost/program_options/options_description.hpp @@ -83,7 +83,8 @@ namespace program_options { /** Given 'option', specified in the input source, return 'true' is 'option' specifies *this. */ - match_result match(const std::string& option, bool approx) const; + match_result match(const std::string& option, bool approx, + bool long_ignore_case, bool short_ignore_case) const; /** Return the key that should identify the option, in particular in the variables_map class. @@ -191,11 +192,15 @@ namespace program_options { */ options_description_easy_init add_options(); - const option_description& find(const std::string& name, bool approx) - const; + const option_description& find(const std::string& name, + bool approx, + bool long_ignore_case = false, + bool short_ignore_case = false) const; const option_description* find_nothrow(const std::string& name, - bool approx) const; + bool approx, + bool long_ignore_case = false, + bool short_ignore_case = false) const; const std::vector< shared_ptr >& options() const; diff --git a/src/cmdline.cpp b/src/cmdline.cpp index 8abc7eb..be31385 100644 --- a/src/cmdline.cpp +++ b/src/cmdline.cpp @@ -172,6 +172,12 @@ namespace boost { namespace program_options { namespace detail { // Need to check that if guessing and long disguise are enabled // -f will mean the same as -foo } + + bool + cmdline::is_style_active(style_t style) const + { + return ((m_style & style) ? true : false); + } void cmdline::set_options_description(const options_description& desc) @@ -284,7 +290,9 @@ namespace boost { namespace program_options { namespace detail { const option_description* xd = m_desc->find_nothrow(opt.string_key, - (m_style & allow_guessing)); + is_style_active(allow_guessing), + is_style_active(long_case_insensitive), + is_style_active(short_case_insensitive)); if (!xd) continue; @@ -348,6 +356,21 @@ namespace boost { namespace program_options { namespace detail { } } } + + // set case sensitive flag + for (unsigned i = 0; i < result.size(); ++i) { + if (result[i].string_key.size() > 2 || + (result[i].string_key.size() > 1 && result[i].string_key[0] != '-')) + { + // it is a long option + result[i].case_insensitive = is_style_active(long_case_insensitive); + } + else + { + // it is a short option + result[i].case_insensitive = is_style_active(short_case_insensitive); + } + } return result; } @@ -361,9 +384,10 @@ namespace boost { namespace program_options { namespace detail { return; // First check that the option is valid, and get its description. - // TODO: case-sensitivity. const option_description* xd = m_desc->find_nothrow(opt.string_key, - (m_style & allow_guessing) ? true : false); + is_style_active(allow_guessing), + is_style_active(long_case_insensitive), + is_style_active(short_case_insensitive)); if (!xd) { @@ -427,7 +451,9 @@ namespace boost { namespace program_options { namespace detail { if (!followed_option.empty()) { const option_description* od = m_desc->find_nothrow(other_tokens[0], - (m_style & allow_guessing) ? true : false); + is_style_active(allow_guessing), + is_style_active(long_case_insensitive), + is_style_active(short_case_insensitive)); if (od) boost::throw_exception(invalid_command_line_syntax(opt.string_key, invalid_command_line_syntax::missing_parameter)); @@ -461,7 +487,7 @@ namespace boost { namespace program_options { namespace detail { adjacent = tok.substr(p+1); if (adjacent.empty()) boost::throw_exception( invalid_command_line_syntax(name, - invalid_command_line_syntax::empty_adjacent_parameter)); + invalid_command_line_syntax::empty_adjacent_parameter) ); } else { @@ -498,7 +524,8 @@ namespace boost { namespace program_options { namespace detail { // option. for(;;) { const option_description* d - = m_desc->find_nothrow(name, false); + = m_desc->find_nothrow(name, false, false, + is_style_active(short_case_insensitive)); // FIXME: check for 'allow_sticky'. if (d && (m_style & allow_sticky) && @@ -563,7 +590,9 @@ namespace boost { namespace program_options { namespace detail { ((m_style & allow_slash_for_short) && tok[0] == '/'))) { if (m_desc->find_nothrow(tok.substr(1, tok.find('=')-1), - (m_style & allow_guessing) ? true : false)) + is_style_active(allow_guessing), + is_style_active(long_case_insensitive), + is_style_active(short_case_insensitive))) { args[0].insert(0, "-"); if (args[0][1] == '/') diff --git a/src/options_description.cpp b/src/options_description.cpp index a9acacf..bfd113d 100644 --- a/src/options_description.cpp +++ b/src/options_description.cpp @@ -28,6 +28,22 @@ using namespace std; namespace boost { namespace program_options { + namespace { + + template< class charT > + std::basic_string< charT > tolower_(const std::basic_string< charT >& str) + { + std::basic_string< charT > result; + for (typename std::basic_string< charT >::size_type i = 0; i < str.size(); ++i) + { + result.append(1, static_cast< charT >(std::tolower(str[i]))); + } + return result; + } + + } // unnamed namespace + + option_description::option_description() { } @@ -55,39 +71,51 @@ namespace boost { namespace program_options { } option_description::match_result - option_description::match(const std::string& option, bool approx) const + option_description::match(const std::string& option, + bool approx, + bool long_ignore_case, + bool short_ignore_case) const { - match_result result = no_match; - if (!m_long_name.empty()) { + match_result result = no_match; + + std::string local_long_name((long_ignore_case ? tolower_(m_long_name) : m_long_name)); + + if (!local_long_name.empty()) { + + std::string local_option = (long_ignore_case ? tolower_(option) : option); - if (*m_long_name.rbegin() == '*') + if (*local_long_name.rbegin() == '*') { // The name ends with '*'. Any specified name with the given // prefix is OK. - if (option.find(m_long_name.substr(0, m_long_name.length()-1)) + if (local_option.find(local_long_name.substr(0, local_long_name.length()-1)) == 0) result = approximate_match; } - if (approx) + if (local_long_name == local_option) { - if (m_long_name.find(option) == 0) - { - if (m_long_name == option) - result = full_match; - else - result = approximate_match; - } + result = full_match; } - else + else if (approx) { - if (m_long_name == option) - result = full_match; + if (local_long_name.find(local_option) == 0) + { + result = approximate_match; + } } } - if (m_short_name == option) - result = full_match; + if (result != full_match) + { + std::string local_option(short_ignore_case ? tolower_(option) : option); + std::string local_short_name(short_ignore_case ? tolower_(m_short_name) : m_short_name); + + if (local_short_name == local_option) + { + result = full_match; + } + } return result; } @@ -253,9 +281,13 @@ namespace boost { namespace program_options { } const option_description& - options_description::find(const std::string& name, bool approx) const + options_description::find(const std::string& name, + bool approx, + bool long_ignore_case, + bool short_ignore_case) const { - const option_description* d = find_nothrow(name, approx); + const option_description* d = find_nothrow(name, approx, + long_ignore_case, short_ignore_case); if (!d) boost::throw_exception(unknown_option(name)); return *d; @@ -269,7 +301,9 @@ namespace boost { namespace program_options { const option_description* options_description::find_nothrow(const std::string& name, - bool approx) const + bool approx, + bool long_ignore_case, + bool short_ignore_case) const { shared_ptr found; vector approximate_matches; @@ -281,7 +315,7 @@ namespace boost { namespace program_options { for(unsigned i = 0; i < m_options.size(); ++i) { option_description::match_result r = - m_options[i]->match(name, approx); + m_options[i]->match(name, approx, long_ignore_case, short_ignore_case); if (r == option_description::no_match) continue; diff --git a/src/variables_map.cpp b/src/variables_map.cpp index 449d23c..29b1de9 100644 --- a/src/variables_map.cpp +++ b/src/variables_map.cpp @@ -58,12 +58,8 @@ namespace boost { namespace program_options { if (xm.m_final.count(name)) continue; - // Ignore options which are not described - //TODO: consider this. - //if (desc.count(name) == 0) - // continue; - - const option_description& d = desc.find(name, false); + const option_description& d = desc.find(name, false, + false, false); variable_value& v = m[name]; if (v.defaulted()) { diff --git a/test/cmdline_test.cpp b/test/cmdline_test.cpp index c6ab492..7c26db2 100644 --- a/test/cmdline_test.cpp +++ b/test/cmdline_test.cpp @@ -218,8 +218,6 @@ void test_long_options() allow_long | long_allow_adjacent | long_allow_next | case_insensitive); -// FIXME: restore -#if 0 // Test case insensitive style. // Note that option names are normalized to lower case. test_case test_cases4[] = { @@ -231,7 +229,6 @@ void test_long_options() {0, 0, 0} }; test_cmdline("foo bar= baz? Giz", style, test_cases4); -#endif } void test_short_options()