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:
Patrick 2023-08-06 13:52:46 +02:00
parent 1549ef27a7
commit a1f1717e22
6 changed files with 346 additions and 1 deletions

View File

@ -4,6 +4,7 @@ Import('env')
mijin_sources = Split("""
source/mijin/async/coroutine.cpp
source/mijin/debug/symbol_info.cpp
source/mijin/io/process.cpp
source/mijin/io/stream.cpp
source/mijin/util/os.cpp
source/mijin/types/name.cpp

175
source/mijin/io/process.cpp Normal file
View 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

View File

@ -0,0 +1,47 @@
#pragma once
#if !defined(MIJIN_IO_PROCESS_HPP_INCLUDED)
#define MIJIN_IO_PROCESS_HPP_INCLUDED 1
#include <vector>
#include "./stream.hpp"
namespace mijin
{
class ProcessStream : public Stream
{
private:
std::FILE* handle = nullptr;
FileOpenMode mode;
int bufferedChar; // for isAtEnd
public:
~ProcessStream() override;
StreamError open(const char* command, FileOpenMode mode_);
inline StreamError open(const std::string& command, FileOpenMode mode_) {
return open(command.c_str(), mode_);
}
inline StreamError open(const std::vector<std::string>& args, FileOpenMode mode_);
int close();
[[nodiscard]] inline bool isOpen() const { return handle != nullptr; }
// Stream overrides
StreamError readRaw(std::span<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override;
StreamError writeRaw(std::span<const std::uint8_t> buffer) override;
std::size_t tell() override;
StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override;
void flush() override;
bool isAtEnd() override;
StreamFeatures getFeatures() override;
};
[[nodiscard]] std::string shellEscape(const std::string& arg) noexcept;
[[nodiscard]] std::string makeShellCommand(const std::vector<std::string>& args) noexcept;
StreamError ProcessStream::open(const std::vector<std::string>& args, FileOpenMode mode_)
{
return open(makeShellCommand(args), mode_);
}
}
#endif // MIJIN_IO_PROCESS_HPP_INCLUDED

View File

@ -48,10 +48,11 @@ enum class FileOpenMode
READ_WRITE
};
enum class StreamError
enum class [[nodiscard]] StreamError
{
SUCCESS,
IO_ERROR,
NOT_SUPPORTED,
UNKNOWN_ERROR
};

View File

@ -4,6 +4,7 @@
#define MIJIN_UTIL_ITERATORS_HPP_INCLUDED 1
#include <cstddef>
#include <functional>
#include <span>
#include <string_view>
#include <tuple>
@ -247,6 +248,94 @@ auto replace(
};
}
template<typename TIterator, typename TFunctor>
struct MappingIterator
{
using orig_value_type = typename std::iterator_traits<TIterator>::value_type;
using difference_type = std::ptrdiff_t;
using value_type = std::invoke_result_t<TFunctor, orig_value_type>;
using iterator_category = std::bidirectional_iterator_tag; // TODO?
TIterator base;
TFunctor functor;
MappingIterator(TIterator base_, TFunctor functor_) noexcept : base(base_), functor(std::move(functor_)) {}
MappingIterator(const MappingIterator&) noexcept = default;
MappingIterator(MappingIterator&&) noexcept = default;
MappingIterator& operator=(const MappingIterator&) noexcept = default;
MappingIterator& operator=(MappingIterator&&) noexcept = default;
value_type operator*() const noexcept
{
return functor(*base);
}
MappingIterator& operator++() noexcept
{
++base;
return *this;
}
MappingIterator operator++(int) noexcept
{
ReplacingIterator copy(*this);
++(*this);
return copy;
}
MappingIterator& operator--() noexcept
{
--base;
return *this;
}
MappingIterator operator--(int) noexcept
{
EnumeratingIterator copy(*this);
--(*this);
return copy;
}
bool operator==(const MappingIterator& other) const noexcept
{
return functor == other.functor && base == other.base;
}
bool operator!=(const MappingIterator& other) const noexcept
{
return !(*this == other);
}
};
template<typename TIterable, typename TFunctor>
struct MappingRange : RangeAdapter
{
using value_type = typename std::iterator_traits<decltype(std::begin(std::declval<TIterable>()))>::value_type;
RangeRef<TIterable> base;
TFunctor functor;
auto begin() const noexcept
{
return MappingIterator(base.begin(), functor);
}
auto end() const noexcept
{
return MappingIterator(base.end(), functor);
}
};
template<typename TIterable, typename TFunctor>
auto map(TIterable&& iterable, TFunctor&& functor)
{
return MappingRange<TIterable, TFunctor>{
.base = {std::forward<TIterable>(iterable)},
.functor = std::forward<TFunctor>(functor)
};
}
template<typename TFirstIterator, typename TSecondIterator>
struct ChainingIterator
{
@ -419,6 +508,22 @@ auto operator|(Chain2<TFirstIterable> chain2, TSecondIterable&& secondIterable)
{
return chain(chain2.range.range, std::forward<TSecondIterable>(secondIterable));
}
template<typename T>
struct Map
{
T functor;
explicit Map(T functor_) : functor(std::move(functor_)) {}
};
template<typename T>
Map(T) -> Map<T>;
template<typename TIterable, typename TFunctor>
auto operator|(TIterable&& iterable, Map<TFunctor> mapper)
{
return map(std::forward<TIterable>(iterable), std::move(mapper.functor));
}
}
} // namespace mijin

View File

@ -47,6 +47,22 @@ std::string join(const TRange& elements, const char* const delimiter)
return oss.str();
}
namespace pipe
{
struct Join
{
const char* delimiter;
explicit Join(const char* delimiter_) noexcept : delimiter(delimiter_) {}
};
template<typename TIterable>
auto operator|(TIterable&& iterable, const Join& joiner)
{
return join(std::forward<TIterable>(iterable), joiner.delimiter);
}
}
} // namespace mijin
#endif // !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)