aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--include/winapi/cmd_line.hpp8
-rw-r--r--src/cmd_line.cpp59
-rw-r--r--test/unit_tests/cmd_line.cpp81
3 files changed, 119 insertions, 29 deletions
diff --git a/include/winapi/cmd_line.hpp b/include/winapi/cmd_line.hpp
index 176cf3d..c6e8c65 100644
--- a/include/winapi/cmd_line.hpp
+++ b/include/winapi/cmd_line.hpp
@@ -29,6 +29,10 @@ public:
CommandLine(std::string&& argv0, std::vector<std::string>&& args = {})
: argv0(std::move(argv0)), args(std::move(args)) {}
+ CommandLine(const std::vector<std::string>& argv);
+
+ CommandLine(std::vector<std::string>&& argv);
+
static std::string escape(const std::string&);
static std::string escape_cmd(const std::string&);
@@ -43,6 +47,8 @@ public:
const std::vector<std::string>& get_args() const { return args; }
+ std::vector<std::string> get_argv() const;
+
private:
static BOOST_CONSTEXPR char token_sep() { return ' '; }
@@ -50,6 +56,8 @@ private:
std::vector<std::string> escape_args() const;
+ std::vector<std::string> escape_argv() const;
+
std::string argv0;
std::vector<std::string> args;
};
diff --git a/src/cmd_line.cpp b/src/cmd_line.cpp
index 468bb96..2f9be41 100644
--- a/src/cmd_line.cpp
+++ b/src/cmd_line.cpp
@@ -57,6 +57,23 @@ CommandLine do_parse(std::wstring src) {
return {std::move(argv0), std::move(args)};
}
+std::string split_argv0(std::vector<std::string>& argv) {
+ if (argv.empty()) {
+ throw std::range_error{"argv must contain at least one element"};
+ }
+ const auto argv0 = argv[0];
+ argv.erase(argv.begin());
+ return argv0;
+}
+
+std::vector<std::string> escape_all(const std::vector<std::string>& xs) {
+ std::vector<std::string> escaped;
+ escaped.reserve(xs.size());
+ for (const auto& x : xs)
+ escaped.emplace_back(CommandLine::escape(x));
+ return escaped;
+}
+
} // namespace
CommandLine CommandLine::query() {
@@ -71,17 +88,21 @@ CommandLine CommandLine::from_main(int argc, wchar_t* argv[]) {
if (argc < 1)
throw std::range_error{"argc must be a positive number"};
- std::string argv0{narrow(argv[0])};
- --argc;
- ++argv;
-
- std::vector<std::string> args;
- args.reserve(argc);
+ std::vector<std::string> utf8_argv;
+ utf8_argv.reserve(argc);
for (int i = 0; i < argc; ++i)
- args.emplace_back(narrow(argv[i]));
+ utf8_argv.emplace_back(narrow(argv[i]));
- return {std::move(argv0), std::move(args)};
+ return {std::move(utf8_argv)};
+}
+
+CommandLine::CommandLine(const std::vector<std::string>& argv) : args(argv) {
+ argv0 = split_argv0(args);
+}
+
+CommandLine::CommandLine(std::vector<std::string>&& argv) : args(std::move(argv)) {
+ argv0 = split_argv0(args);
}
std::string CommandLine::escape(const std::string& arg) {
@@ -135,23 +156,25 @@ std::string CommandLine::escape_cmd(const std::string& arg) {
}
std::string CommandLine::to_string() const {
- std::ostringstream oss;
- oss << escape(get_argv0());
- if (has_args())
- oss << token_sep() << args_to_string();
- return oss.str();
+ return boost::algorithm::join(escape_argv(), std::string{token_sep()});
}
std::string CommandLine::args_to_string() const {
return boost::algorithm::join(escape_args(), std::string{token_sep()});
}
+std::vector<std::string> CommandLine::get_argv() const {
+ auto argv = get_args();
+ argv.emplace(argv.begin(), get_argv0());
+ return argv;
+}
+
std::vector<std::string> CommandLine::escape_args() const {
- std::vector<std::string> safe;
- safe.reserve(args.size());
- for (const auto& arg : args)
- safe.emplace_back(escape(arg));
- return safe;
+ return escape_all(get_args());
+}
+
+std::vector<std::string> CommandLine::escape_argv() const {
+ return escape_all(get_argv());
}
} // namespace winapi
diff --git a/test/unit_tests/cmd_line.cpp b/test/unit_tests/cmd_line.cpp
index 99646ff..42f4a93 100644
--- a/test/unit_tests/cmd_line.cpp
+++ b/test/unit_tests/cmd_line.cpp
@@ -5,30 +5,89 @@
#include <winapi/cmd_line.hpp>
+// clang-format off
+// The order matters for older Boost versions.
#include <boost/test/unit_test.hpp>
+#include <boost/test/data/test_case.hpp>
+#include <boost/test/data/monomorphic.hpp>
+// clang-format on
+#include <ostream>
#include <string>
+#include <vector>
+
+using namespace winapi;
+
+namespace std {
+
+ostream& operator<<(ostream& os, const vector<string>& xs) {
+ os << "[\n";
+ for (const auto& x : xs) {
+ os << x << '\n';
+ }
+ os << "]";
+ return os;
+}
+
+} // namespace std
+
+BOOST_TEST_SPECIALIZED_COLLECTION_COMPARE(std::vector<std::string>);
+
+namespace {
+
+// MSDN examples
+// https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments?view=vs-2019
+
+const std::vector<std::string> msdn_string{
+ R"(test.exe "abc" d e)",
+ R"(test.exe a\\\b d"e f"g h)",
+ R"(test.exe a\\\"b c d)",
+ R"(test.exe a\\\\"b c" d e)",
+};
+
+const std::vector<std::vector<std::string>> msdn_argv{
+ {"test.exe", "abc", "d", "e"},
+ {"test.exe", R"(a\\\b)", "de fg", "h"},
+ {"test.exe", R"(a\"b)", "c", "d"},
+ {"test.exe", R"(a\\b c)", "d", "e"},
+};
+
+} // namespace
BOOST_AUTO_TEST_SUITE(cmd_line_tests)
BOOST_AUTO_TEST_CASE(query) {
- const auto cmd_line = winapi::CommandLine::query();
- BOOST_TEST(!cmd_line.get_argv0().empty());
- BOOST_TEST_MESSAGE(cmd_line.get_argv0());
+ const auto cmd_line = CommandLine::query();
+ BOOST_TEST(!cmd_line.get_argv0().empty(), "argv[0]: " << cmd_line.get_argv0());
}
-BOOST_AUTO_TEST_CASE(escape) {
- wchar_t* argv[] = {
- L"test.exe",
- L"arg1 arg2",
- LR"(path\to\file)",
- LR"(path\to\dir\)",
- LR"(weird\\argument)",
+BOOST_AUTO_TEST_CASE(to_string) {
+ const std::vector<std::string> argv{
+ "test.exe",
+ "arg1 arg2",
+ R"(path\to\file)",
+ R"(path\to\dir\)",
+ R"(weird\\argument)",
};
- const auto cmd_line = winapi::CommandLine::from_main(5, argv);
+ const CommandLine cmd_line{argv};
const auto expected =
R"("test.exe" "arg1 arg2" "path\to\file" "path\to\dir\\" "weird\\argument")";
BOOST_TEST(cmd_line.to_string() == expected);
}
+BOOST_DATA_TEST_CASE(msdn_parse,
+ boost::unit_test::data::make(msdn_string) ^ msdn_argv,
+ input,
+ expected) {
+ const auto cmd_line = CommandLine::parse(input);
+ const auto actual = cmd_line.get_argv();
+ BOOST_TEST(actual == expected, "actual: " << actual);
+}
+
+BOOST_DATA_TEST_CASE(msdn_to_string, msdn_argv, argv) {
+ const CommandLine cmd_line{argv};
+ const auto actual = CommandLine::parse(cmd_line.to_string()).get_argv();
+ BOOST_TEST(actual == argv, "actual: " << actual);
+}
+
BOOST_AUTO_TEST_SUITE_END()