diff --git a/build/Jamfile b/build/Jamfile index a9aee58..7f39992 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -3,6 +3,7 @@ subproject libs/program_options/build ; SOURCES = cmdline config_file options_description parsers variables_map value_semantic positional_options utf8_codecvt_facet convert + winmain ; lib boost_program_options diff --git a/build/Jamfile.v2 b/build/Jamfile.v2 index 3bf3280..414ec86 100644 --- a/build/Jamfile.v2 +++ b/build/Jamfile.v2 @@ -5,7 +5,7 @@ project boost/program_options SOURCES = cmdline config_file options_description parsers variables_map value_semantic positional_options utf8_codecvt_facet - convert + convert winmain ; import os ; diff --git a/include/boost/program_options/parsers.hpp b/include/boost/program_options/parsers.hpp index b0c0ef8..5d48091 100644 --- a/include/boost/program_options/parsers.hpp +++ b/include/boost/program_options/parsers.hpp @@ -173,6 +173,21 @@ namespace boost { namespace program_options { BOOST_PROGRAM_OPTIONS_DECL parsed_options parse_environment(const options_description&, const char* prefix); + #ifdef _WIN32 + /** Parses the char* string which is passed to WinMain function on + windows. This function is provided for convenience, and because it's + not clear how to portably access split command line string from + runtime library and if it always exists. + This function is available only on Windows. + */ + BOOST_PROGRAM_OPTIONS_DECL std::vector + split_winmain(const std::string& cmdline); + + /** @overload */ + BOOST_PROGRAM_OPTIONS_DECL std::vector + split_winmain(const std::wstring& cmdline); + #endif + }} #undef DECL diff --git a/src/winmain.cpp b/src/winmain.cpp new file mode 100644 index 0000000..e8a20f8 --- /dev/null +++ b/src/winmain.cpp @@ -0,0 +1,93 @@ +// Copyright Vladimir Prus 2002-2004. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#define BOOST_PROGRAM_OPTIONS_SOURCE +#include + +#ifdef _WIN32 +namespace boost { namespace program_options { + + using namespace std; + + // The rules for windows command line are pretty funny, see + // http://article.gmane.org/gmane.comp.lib.boost.user/3005 + // http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp + + BOOST_PROGRAM_OPTIONS_DECL + std::vector split_winmain(const std::string& input) + { + std::vector result; + + string::const_iterator i = input.begin(), e = input.end(); + for(;i != e; ++i) + if (!std::isspace(*i)) + break; + + if (i != e) { + + std::string current; + bool inside_quoted = false; + int backslash_count = 0; + + for(; i != e; ++i) { + if (*i == '"') { + // '"' preceded by even number (n) of backslashes generates + // n/2 backslashes and is a quoted block delimiter + if (backslash_count % 2 == 0) { + current.append(backslash_count / 2, '\\'); + inside_quoted = !inside_quoted; + // '"' preceded by odd number (n) of backslashes generates + // (n-1)/2 backslashes and is literal quote. + } else { + current.append(backslash_count / 2, '\\'); + current.push_back('"'); + } + backslash_count = 0; + } else if (*i == '\\') { + ++backslash_count; + } else { + // Not quote or backslash. All accumulated backslashes should be + // added + if (backslash_count) { + current.append(backslash_count, '\\'); + backslash_count = 0; + } + if (isspace(*i) && !inside_quoted) { + // Space outside quoted section terminate the current argument + result.push_back(current); + current.resize(0); + for(;i != e && isspace(*i); ++i) + ; + --i; + } else { + current.push_back(*i); + } + } + } + + // If we have trailing backslashes, add them + if (backslash_count) + current.append(backslash_count, '\\'); + + // If we have non-empty 'current' or we're still in quoted + // section (even if 'current' is empty), add the last token. + if (!current.empty() || inside_quoted) + result.push_back(current); + } + return result; + } + + BOOST_PROGRAM_OPTIONS_DECL std::vector + split_winmain(const std::wstring& cmdline) + { + vector result; + vector aux = split_winmain(to_internal(cmdline)); + for (unsigned i = 0, e = result.size(); i < e; ++i) + result.push_back(from_utf8(aux[i])); + return result; + } + +}} +#endif diff --git a/test/Jamfile b/test/Jamfile index 886cb1c..0da99f3 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -33,12 +33,14 @@ test-suite program_options : [ program-options-test cmdline_test ] [ program-options-test positional_options_test ] [ program-options-test unicode_test ] + [ program-options-test winmain ] [ program-options-dll-test options_description_test ] [ program-options-dll-test parsers_test ] [ program-options-dll-test variable_map_test ] [ program-options-dll-test cmdline_test ] [ program-options-dll-test positional_options_test ] [ program-options-dll-test unicode_test ] + [ program-options-dll-test winmain ] ; diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 076b1ea..948a005 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -14,6 +14,7 @@ test-suite program_options : [ run cmdline_test.cpp ] [ run positional_options_test.cpp ] [ run unicode_test.cpp ] + [ run winmain.cpp ] ; exe test_convert : test_convert.cpp ../build//program_options ; diff --git a/test/winmain.cpp b/test/winmain.cpp new file mode 100644 index 0000000..f178552 --- /dev/null +++ b/test/winmain.cpp @@ -0,0 +1,63 @@ +// Copyright Vladimir Prus 2002-2004. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifdef _WIN32 +#include +#include +#include + +#include +using namespace boost::program_options; + +#include +#include + +template +unsigned size(const T (&t)[N]) +{ + return N; +} + +void test_winmain() +{ + using namespace std; + +#define C , +#define TEST(input, expected) \ + char* BOOST_PP_CAT(e, __LINE__)[] = expected;\ + vector BOOST_PP_CAT(v, __LINE__) = split_winmain(input);\ + BOOST_REQUIRE(BOOST_PP_CAT(v, __LINE__).size() == size(BOOST_PP_CAT(e, __LINE__)));\ + BOOST_CHECK_EQUAL_COLLECTIONS(BOOST_PP_CAT(v, __LINE__).begin(),\ + BOOST_PP_CAT(v, __LINE__).end(),\ + BOOST_PP_CAT(e, __LINE__)); + +// The following expectations were obtained in Win2000 shell: + TEST("1 ", {"1"}); + TEST("1\"2\" ", {"12"}); + TEST("1\"2 ", {"12 "}); + TEST("1\"\\\"2\" ", {"1\"2"}); + TEST("\"1\" \"2\" ", {"1" C "2"}); + TEST("1\\\" ", {"1\""}); + TEST("1\\\\\" ", {"1\\ "}); + TEST("1\\\\\\\" ", {"1\\\""}); + TEST("1\\\\\\\\\" ", {"1\\\\ "}); + + TEST("1\" 1 ", {"1 1 "}); + TEST("1\\\" 1 ", {"1\"" C "1"}); + TEST("1\\1 ", {"1\\1"}); + TEST("1\\\\1 ", {"1\\\\1"}); +} + +int test_main(int, char*[]) +{ + test_winmain(); + return 0; +} +#else +int test_main(int, char*[]) +{ + return 0; +} +#endif