Added ProcessStream to for running processes (on Linux) and streaming their output.
Added map() function for creating a mapping iterator. Added mijin::pipe types for map() and join().
This commit is contained in:
175
source/mijin/io/process.cpp
Normal file
175
source/mijin/io/process.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
|
||||
#include "./process.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include "../util/iterators.hpp"
|
||||
#include "../util/string.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
ProcessStream::~ProcessStream()
|
||||
{
|
||||
if (handle) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
StreamError ProcessStream::open(const char* command, FileOpenMode mode_)
|
||||
{
|
||||
mode = mode_;
|
||||
|
||||
const char* modeStr; // NOLINT(cppcoreguidelines-init-variables)
|
||||
switch (mode_)
|
||||
{
|
||||
case FileOpenMode::READ:
|
||||
modeStr = "r";
|
||||
break;
|
||||
case FileOpenMode::WRITE:
|
||||
modeStr = "w";
|
||||
break;
|
||||
case FileOpenMode::READ_WRITE:
|
||||
modeStr = "rw";
|
||||
break;
|
||||
default:
|
||||
assert(!"Unsupported mode for ProcessStream::open()");
|
||||
return StreamError::NOT_SUPPORTED;
|
||||
}
|
||||
handle = popen(command, modeStr); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
if (!handle) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
int ProcessStream::close()
|
||||
{
|
||||
assert(handle);
|
||||
const int result = pclose(handle);
|
||||
handle = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
StreamError ProcessStream::readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead)
|
||||
{
|
||||
assert(handle);
|
||||
assert(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE);
|
||||
|
||||
if (bufferedChar >= 0 && buffer.size() > 0)
|
||||
{
|
||||
buffer[0] = static_cast<std::uint8_t>(bufferedChar);
|
||||
}
|
||||
|
||||
const std::size_t readBytes = std::fread(buffer.data(), 1, buffer.size(), handle);
|
||||
if (std::ferror(handle)) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
if (!partial && readBytes < buffer.size()) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
if (outBytesRead != nullptr)
|
||||
{
|
||||
*outBytesRead = readBytes + (bufferedChar >= 0 ? 1 : 0);
|
||||
}
|
||||
bufferedChar = -1;
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError ProcessStream::writeRaw(std::span<const std::uint8_t> buffer)
|
||||
{
|
||||
assert(handle);
|
||||
assert(mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE);
|
||||
|
||||
const std::size_t written = std::fwrite(buffer.data(), 1, buffer.size(), handle);
|
||||
if (written != buffer.size() || std::ferror(handle)) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
std::size_t ProcessStream::tell()
|
||||
{
|
||||
assert(handle);
|
||||
|
||||
return std::ftell(handle); // TODO: does this work?
|
||||
}
|
||||
|
||||
StreamError ProcessStream::seek(std::intptr_t pos, SeekMode seekMode)
|
||||
{
|
||||
(void) pos;
|
||||
(void) seekMode;
|
||||
return StreamError::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
void ProcessStream::flush()
|
||||
{
|
||||
const int result = std::fflush(handle);
|
||||
assert(result == 0);
|
||||
}
|
||||
|
||||
bool ProcessStream::isAtEnd()
|
||||
{
|
||||
assert(handle);
|
||||
|
||||
if (bufferedChar >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bufferedChar = std::fgetc(handle);
|
||||
if (std::feof(handle)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
StreamFeatures ProcessStream::getFeatures()
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
return {
|
||||
.read = (mode == FileOpenMode::READ),
|
||||
.write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE),
|
||||
.tell = true,
|
||||
.seek = false
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string shellEscape(const std::string& arg) noexcept
|
||||
{
|
||||
std::ostringstream oss;
|
||||
const bool requiresQuotes = std::any_of(arg.begin(), arg.end(), [&](const char chr) { return std::isspace(chr); });
|
||||
if (requiresQuotes) {
|
||||
oss << '"';
|
||||
}
|
||||
for (const char chr : arg)
|
||||
{
|
||||
// TODO: oh Windows this will probably be different
|
||||
switch (chr)
|
||||
{
|
||||
case '"':
|
||||
case '\'':
|
||||
case '\\':
|
||||
case '$':
|
||||
oss << '\\';
|
||||
break;
|
||||
}
|
||||
oss << chr;
|
||||
}
|
||||
if (requiresQuotes) {
|
||||
oss << '"';
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string makeShellCommand(const std::vector<std::string>& args) noexcept
|
||||
{
|
||||
using namespace mijin::pipe;
|
||||
return args
|
||||
| Map(&shellEscape)
|
||||
| Join(" ");
|
||||
}
|
||||
} // namespace mijin
|
||||
Reference in New Issue
Block a user