add command line splitting api (posix, windows) and use it from build

master
Daniel Kolesa 2017-05-07 21:16:42 +02:00
parent a191164700
commit f01992c952
3 changed files with 208 additions and 8 deletions

View File

@ -11,7 +11,6 @@
#include <memory>
#include <unistd.h>
#include <wordexp.h>
#include <sys/wait.h>
#include "ostd/io.hh"
@ -26,6 +25,7 @@ namespace fs = ostd::filesystem;
/* ugly, but do not explicitly compile */
#include "src/io.cc"
#include "src/process.cc"
using strvec = std::vector<std::string>;
using pathvec = std::vector<fs::path>;
@ -145,13 +145,9 @@ static std::string get_command(std::string const &cmd, strvec const &args) {
}
static void add_args(strvec &args, std::string const &cmdl) {
wordexp_t p;
if (wordexp(cmdl.data(), &p, 0)) {
return;
}
for (std::size_t i = 0; i < p.we_wordc; ++i) {
args.push_back(p.we_wordv[i]);
}
auto app = ostd::appender(std::move(args));
ostd::split_args(app, cmdl);
args = std::move(app.get());
}
static fs::path path_with_ext(fs::path const &p, fs::path const &ext) {

78
ostd/process.hh 100644
View File

@ -0,0 +1,78 @@
/** @addtogroup Utilities
* @{
*/
/** @file process.hh
*
* @brief Portable extensions to process handling.
*
* Provides POSIX and Windows abstractions for process creation and more.
*
* @copyright See COPYING.md in the project tree for further information.
*/
#ifndef OSTD_PROCESS_HH
#define OSTD_PROCESS_HH
#include <stdexcept>
#include "ostd/platform.hh"
#include "ostd/string.hh"
namespace ostd {
/** @addtogroup Utilities
* @{
*/
struct word_error: std::runtime_error {
using std::runtime_error::runtime_error;
};
namespace detail {
OSTD_EXPORT void split_args_impl(
string_range const &str, void (*func)(string_range, void *), void *data
);
}
/** @brief Splits command line argument string into individual arguments.
*
* The split is done in a platform specific manner, using wordexp on POSIX
* systems and CommandLineToArgvW on Windows. On Windows, the input string
* is assumed to be UTF-8 and the output strings are always UTF-8, on POSIX
* the wordexp implementation is followed (so it's locale specific).
*
* The `out` argument is an output range that takes a single argument at
* a time. The value type is any that can be explicitly constructed from
* an ostd::string_range. However, the string range that is used internally
* during the conversions is just temporary and freed at the end of this
* function, so it's important that the string type holds its own memory;
* an std::string will usually suffice.
*
* The ostd::word_error exception is used to handle failures of this
* function itself. It may also throw other exceptions though, particularly
* those thrown by `out` when putting the strings into it and also
* std::bad_alloc on allocation failures.
*
* @returns The forwarded `out`.
*
* @throws ostd::word_error on failure or anything thrown by `out`.
* @throws std::bad_alloc on alloation failures.
*/
template<typename OutputRange>
OutputRange &&split_args(OutputRange &&out, string_range str) {
detail::split_args_impl(str, [](string_range val, void *outp) {
static_cast<std::decay_t<OutputRange> *>(outp)->put(
range_value_t<std::decay_t<OutputRange>>{val}
);
}, &out);
return std::forward<OutputRange>(out);
}
/** @} */
} /* namespace ostd */
#endif
/** @} */

126
src/process.cc 100644
View File

@ -0,0 +1,126 @@
/* Process handling implementation bits.
*
* This file is part of libostd. See COPYING.md for futher information.
*/
#include <cstddef>
#include <string>
#include <memory>
#include <new>
#include "ostd/process.hh"
#ifdef OSTD_PLATFORM_WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else
# include <unistd.h>
# include <wordexp.h>
#endif
namespace ostd {
namespace detail {
OSTD_EXPORT void split_args_impl(
string_range const &str, void (*func)(string_range, void *), void *data
) {
#ifndef OSTD_PLATFORM_WIN32
std::string strs{str};
wordexp_t p;
if (int err; (err = wordexp(strs.data(), &p, 0))) {
switch (err) {
case WRDE_BADCHAR:
throw word_error{"illegal character"};
case WRDE_BADVAL:
/* no WRDE_UNDEF flag, won't happen */
throw word_error{"undefined shell variable"};
case WRDE_CMDSUB:
/* no WRDE_NOCMD flag, won't happen */
throw word_error{"invalid command substitution"};
case WRDE_NOSPACE:
throw std::bad_alloc{};
case WRDE_SYNTAX:
throw word_error{"syntax error"};
default:
throw word_error{"unknown error"};
}
}
/* if the output range throws, make sure stuff gets freed */
struct wordexp_guard {
void operator()(wordexp_t *fp) const {
wordfree(fp);
}
};
std::unique_ptr<wordexp_t, wordexp_guard> guard{&p};
for (std::size_t i = 0; i < p.we_wordc; ++i) {
func(p.we_wordv[i], data);
}
#else
std::unique_ptr<wchar_t[]> wstr{new wchar_t[str.size() + 1]};
memset(wstr.get(), 0, (str.size() + 1) * sizeof(wchar_t));
if (!MultiByteToWideChar(
CP_UTF8, 0, str.data(), str.size(), wstr.get(), str.size() + 1
)) {
switch (GetLastError()) {
case ERROR_NO_UNICODE_TRANSLATION:
throw word_error{"unicode conversion failed"};
default:
throw word_error{"unknown error"};
}
}
int argc = 0;
wchar_t **pwargs = CommandLineToArgvW(wstr.get(), &argc);
guard.reset());
if (!pwargs) {
throw word_error{"command line parsing failed"};
}
/* if anything throws, make sure stuff gets freed */
struct wchar_guard {
void operator()(wchar_t **p) const {
LocalFree(p);
}
};
std::unique_ptr<wchar_t *, wchar_guard> wguard{pwargs};
for (int i = 0; i < argc; ++i) {
wchar_t *arg = pwargs[i];
std::size_t arglen = wcslen(arg);
std::size_t req = 0;
if (!(req = WideCharToMultiByte(
CP_UTF8, 0, arg, arglen, nullptr, 0, nullptr, nullptr
))) {
switch (GetLastError()) {
case ERROR_NO_UNICODE_TRANSLATION:
throw word_error{"unicode conversion failed"};
default:
throw word_error{"unknown error"};
}
}
std::unique_ptr<char[]> buf{new char[req]};
if (!WideCharToMultiByte(
CP_UTF8, 0, arg, arglen, buf.get(), req, nullptr, nullptr
)) {
switch (GetLastError()) {
case ERROR_NO_UNICODE_TRANSLATION:
throw word_error{"unicode conversion failed"};
default:
throw word_error{"unknown error"};
}
}
func(string_range{buf.get(), buf.get() + req - 1}, data);
}
#endif
}
} /* namespace detail */
} /* namespace ostd */