Add simple binary io library
This commit is contained in:
parent
401e0a29e2
commit
22ef7b45a8
11 changed files with 548 additions and 0 deletions
7
libs/io/CMakeLists.txt
Normal file
7
libs/io/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
file(GLOB_RECURSE PSEMEK_IO_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp")
|
||||
file(GLOB_RECURSE PSEMEK_IO_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp")
|
||||
|
||||
psemek_add_library(psemek-io ${PSEMEK_IO_HEADERS} ${PSEMEK_IO_SOURCES})
|
||||
target_include_directories(psemek-io PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
|
||||
psemek_glob_tests(psemek-io tests)
|
||||
24
libs/io/include/psemek/io/error.hpp
Normal file
24
libs/io/include/psemek/io/error.hpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
|
||||
namespace psemek::io
|
||||
{
|
||||
|
||||
struct null_stream
|
||||
: std::exception
|
||||
{};
|
||||
|
||||
struct null_istream
|
||||
: null_stream
|
||||
{
|
||||
char const * what() const noexcept override;
|
||||
};
|
||||
|
||||
struct null_ostream
|
||||
: null_stream
|
||||
{
|
||||
char const * what() const noexcept override;
|
||||
};
|
||||
|
||||
}
|
||||
95
libs/io/include/psemek/io/file_stream.hpp
Normal file
95
libs/io/include/psemek/io/file_stream.hpp
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/io/stream.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
#include <cstdio>
|
||||
|
||||
namespace psemek::io
|
||||
{
|
||||
|
||||
struct file_istream
|
||||
: istream
|
||||
{
|
||||
file_istream() = default;
|
||||
|
||||
file_istream(FILE * file)
|
||||
: file_{file}
|
||||
{}
|
||||
|
||||
file_istream(std::filesystem::path const & path);
|
||||
|
||||
file_istream(file_istream && s)
|
||||
: file_{s.file_}
|
||||
{
|
||||
s.file_ = nullptr;
|
||||
}
|
||||
|
||||
file_istream & operator = (file_istream && s)
|
||||
{
|
||||
if (this != &s)
|
||||
{
|
||||
reset();
|
||||
file_ = s.file_;
|
||||
s.file_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset();
|
||||
|
||||
std::size_t read(char * p, std::size_t size) override;
|
||||
|
||||
~file_istream() override
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
private:
|
||||
FILE * file_ = nullptr;
|
||||
};
|
||||
|
||||
struct file_ostream
|
||||
: ostream
|
||||
{
|
||||
static constexpr unsigned append = 1;
|
||||
|
||||
file_ostream() = default;
|
||||
|
||||
file_ostream(FILE * file)
|
||||
: file_{file}
|
||||
{}
|
||||
|
||||
file_ostream(std::filesystem::path const & path, unsigned flags = 0);
|
||||
|
||||
file_ostream(file_ostream && s)
|
||||
: file_{s.file_}
|
||||
{
|
||||
s.file_ = nullptr;
|
||||
}
|
||||
|
||||
file_ostream & operator = (file_ostream && s)
|
||||
{
|
||||
if (this != &s)
|
||||
{
|
||||
reset();
|
||||
file_ = s.file_;
|
||||
s.file_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset();
|
||||
|
||||
std::size_t write(char const * p, std::size_t size) override;
|
||||
|
||||
~file_ostream() override
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
private:
|
||||
FILE * file_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
111
libs/io/include/psemek/io/memory_stream.hpp
Normal file
111
libs/io/include/psemek/io/memory_stream.hpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/io/stream.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace psemek::io
|
||||
{
|
||||
|
||||
struct memory_istream
|
||||
: istream
|
||||
{
|
||||
memory_istream() = default;
|
||||
|
||||
memory_istream(std::string_view str)
|
||||
: begin_{str.data()}
|
||||
, end_{str.data() + str.size()}
|
||||
{}
|
||||
|
||||
memory_istream(char const * begin, char const * end)
|
||||
: begin_{begin}
|
||||
, end_{end}
|
||||
{}
|
||||
|
||||
memory_istream(memory_istream && s)
|
||||
: begin_{s.begin_}
|
||||
, end_{s.end_}
|
||||
{
|
||||
s.reset();
|
||||
}
|
||||
|
||||
memory_istream & operator = (memory_istream && s)
|
||||
{
|
||||
if (this != &s)
|
||||
{
|
||||
begin_ = s.begin_;
|
||||
end_ = s.end_;
|
||||
s.reset();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
begin_ = nullptr;
|
||||
end_ = nullptr;
|
||||
}
|
||||
|
||||
void swap(memory_istream & s)
|
||||
{
|
||||
std::swap(begin_, s.begin_);
|
||||
std::swap(end_, s.end_);
|
||||
}
|
||||
|
||||
std::size_t read(char * p, std::size_t size) override;
|
||||
|
||||
private:
|
||||
char const * begin_ = nullptr;
|
||||
char const * end_ = nullptr;
|
||||
};
|
||||
|
||||
struct memory_ostream
|
||||
: ostream
|
||||
{
|
||||
memory_ostream() = default;
|
||||
|
||||
memory_ostream(char * begin, char * end)
|
||||
: begin_{begin}
|
||||
, end_{end}
|
||||
{}
|
||||
|
||||
memory_ostream(memory_ostream && s)
|
||||
: begin_{s.begin_}
|
||||
, end_{s.end_}
|
||||
{
|
||||
s.reset();
|
||||
}
|
||||
|
||||
memory_ostream & operator = (memory_ostream && s)
|
||||
{
|
||||
if (this != &s)
|
||||
{
|
||||
begin_ = s.begin_;
|
||||
end_ = s.end_;
|
||||
s.reset();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
begin_ = nullptr;
|
||||
end_ = nullptr;
|
||||
}
|
||||
|
||||
void swap(memory_ostream & s)
|
||||
{
|
||||
std::swap(begin_, s.begin_);
|
||||
std::swap(end_, s.end_);
|
||||
}
|
||||
|
||||
std::size_t write(char const * p, std::size_t size) override;
|
||||
|
||||
private:
|
||||
char * begin_ = nullptr;
|
||||
char * end_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
29
libs/io/include/psemek/io/stream.hpp
Normal file
29
libs/io/include/psemek/io/stream.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/io/error.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
namespace psemek::io
|
||||
{
|
||||
|
||||
struct istream
|
||||
{
|
||||
virtual std::size_t read(char * p, std::size_t size) = 0;
|
||||
|
||||
virtual ~istream() {}
|
||||
};
|
||||
|
||||
struct ostream
|
||||
{
|
||||
virtual std::size_t write(char const * p, std::size_t size) = 0;
|
||||
|
||||
virtual ~ostream() {}
|
||||
};
|
||||
|
||||
std::unique_ptr<istream> std_in();
|
||||
std::unique_ptr<ostream> std_out();
|
||||
std::unique_ptr<ostream> std_err();
|
||||
|
||||
}
|
||||
16
libs/io/source/error.cpp
Normal file
16
libs/io/source/error.cpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#include <psemek/io/error.hpp>
|
||||
|
||||
namespace psemek::io
|
||||
{
|
||||
|
||||
char const * null_istream::what() const noexcept
|
||||
{
|
||||
return "Attempt to read from null input stream";
|
||||
}
|
||||
|
||||
char const * null_ostream::what() const noexcept
|
||||
{
|
||||
return "Attempt to write to null output stream";
|
||||
}
|
||||
|
||||
}
|
||||
64
libs/io/source/file_stream.cpp
Normal file
64
libs/io/source/file_stream.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include <psemek/io/file_stream.hpp>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace psemek::io
|
||||
{
|
||||
|
||||
static void throw_fopen [[noreturn]] (std::filesystem::path const & path)
|
||||
{
|
||||
throw std::runtime_error("Failed to open " + path.native() + ": " + std::string(std::strerror(errno)));
|
||||
}
|
||||
|
||||
static FILE * safe_fopen(std::filesystem::path const & path, const char * mode)
|
||||
{
|
||||
auto f = std::fopen(path.c_str(), mode);
|
||||
if (!f) throw_fopen(path);
|
||||
return f;
|
||||
}
|
||||
|
||||
file_istream::file_istream(std::filesystem::path const & path)
|
||||
: file_{safe_fopen(path.c_str(), "rb")}
|
||||
{}
|
||||
|
||||
void file_istream::reset()
|
||||
{
|
||||
if (file_)
|
||||
std::fclose(file_);
|
||||
file_ = nullptr;
|
||||
}
|
||||
|
||||
std::size_t file_istream::read(char * p, std::size_t size)
|
||||
{
|
||||
if (!file_) throw null_istream{};
|
||||
return std::fread(p, 1, size, file_);
|
||||
}
|
||||
|
||||
static char const * fopen_write_mode(unsigned flags)
|
||||
{
|
||||
switch (flags)
|
||||
{
|
||||
case 0: return "wb";
|
||||
case file_ostream::append: return "ab";
|
||||
default: throw std::runtime_error("Unknown file_ostream open flags");
|
||||
}
|
||||
}
|
||||
|
||||
file_ostream::file_ostream(std::filesystem::path const & path, unsigned flags)
|
||||
: file_{safe_fopen(path.c_str(), fopen_write_mode(flags))}
|
||||
{}
|
||||
|
||||
void file_ostream::reset()
|
||||
{
|
||||
if (file_)
|
||||
std::fclose(file_);
|
||||
file_ = nullptr;
|
||||
}
|
||||
|
||||
std::size_t file_ostream::write(char const * p, std::size_t size)
|
||||
{
|
||||
if (!file_) throw null_ostream{};
|
||||
return std::fwrite(p, 1, size, file_);
|
||||
}
|
||||
|
||||
}
|
||||
26
libs/io/source/memory_stream.cpp
Normal file
26
libs/io/source/memory_stream.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#include <psemek/io/memory_stream.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace psemek::io
|
||||
{
|
||||
|
||||
std::size_t memory_istream::read(char * p, std::size_t size)
|
||||
{
|
||||
if (!begin_) throw null_istream{};
|
||||
size = std::min<std::size_t>(size, end_ - begin_);
|
||||
std::copy(begin_, begin_ + size, p);
|
||||
begin_ += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
std::size_t memory_ostream::write(char const * p, std::size_t size)
|
||||
{
|
||||
if (!begin_) throw null_ostream{};
|
||||
size = std::min<std::size_t>(size, end_ - begin_);
|
||||
std::copy(p, p + size, begin_);
|
||||
begin_ += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
}
|
||||
22
libs/io/source/stream.cpp
Normal file
22
libs/io/source/stream.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#include <psemek/io/file_stream.hpp>
|
||||
|
||||
namespace psemek::io
|
||||
{
|
||||
|
||||
std::unique_ptr<istream> std_in()
|
||||
{
|
||||
return std::make_unique<file_istream>(stdin);
|
||||
}
|
||||
|
||||
std::unique_ptr<ostream> std_out()
|
||||
{
|
||||
return std::make_unique<file_ostream>(stdout);
|
||||
}
|
||||
|
||||
std::unique_ptr<ostream> std_err()
|
||||
{
|
||||
return std::make_unique<file_ostream>(stderr);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
90
libs/io/tests/file.cpp
Normal file
90
libs/io/tests/file.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#include <psemek/test/test.hpp>
|
||||
|
||||
#include <psemek/io/file_stream.hpp>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
using namespace psemek::io;
|
||||
|
||||
test_case(io_file_null)
|
||||
{
|
||||
char buffer[256]{};
|
||||
|
||||
file_istream is;
|
||||
expect_throw(is.read(buffer, std::size(buffer)), null_istream);
|
||||
|
||||
file_ostream os;
|
||||
expect_throw(os.write(buffer, std::size(buffer)), null_ostream);
|
||||
}
|
||||
|
||||
static std::filesystem::path temp_file()
|
||||
{
|
||||
return std::filesystem::temp_directory_path() / "psemek_test_io";
|
||||
}
|
||||
|
||||
test_case(io_file_write)
|
||||
{
|
||||
char const test_str[] = "Hello, world!";
|
||||
auto const path = temp_file();
|
||||
auto const length = std::size(test_str);
|
||||
|
||||
{
|
||||
file_ostream os(path);
|
||||
expect_equal(os.write(test_str, length), length);
|
||||
}
|
||||
|
||||
expect(std::filesystem::exists(path));
|
||||
|
||||
char buffer_in[256]{};
|
||||
|
||||
std::ifstream is(path);
|
||||
expect(is.read(buffer_in, length));
|
||||
expect_equal(std::string_view(test_str, length), std::string_view(buffer_in, length));
|
||||
}
|
||||
|
||||
test_case(io_file_append)
|
||||
{
|
||||
char const test_str[] = "Hello, world!";
|
||||
auto const path = temp_file();
|
||||
auto const length = std::size(test_str);
|
||||
auto const half_length = length / 2;
|
||||
auto const remain_length = length - half_length;
|
||||
|
||||
{
|
||||
file_ostream os(path);
|
||||
expect_equal(os.write(test_str, half_length), half_length);
|
||||
}
|
||||
|
||||
expect(std::filesystem::exists(path));
|
||||
|
||||
{
|
||||
file_ostream os(path, file_ostream::append);
|
||||
expect_equal(os.write(test_str + half_length, remain_length), remain_length);
|
||||
}
|
||||
|
||||
expect(std::filesystem::exists(path));
|
||||
|
||||
char buffer_in[256]{};
|
||||
|
||||
std::ifstream is(path);
|
||||
expect(is.read(buffer_in, length));
|
||||
expect_equal(std::string_view(test_str, length), std::string_view(buffer_in, length));
|
||||
}
|
||||
|
||||
test_case(io_file_read)
|
||||
{
|
||||
char const test_str[] = "Hello, world!";
|
||||
auto const path = temp_file();
|
||||
auto const length = std::size(test_str);
|
||||
|
||||
{
|
||||
std::ofstream os(path);
|
||||
expect(os.write(test_str, length));
|
||||
}
|
||||
|
||||
char buffer_in[256]{};
|
||||
|
||||
file_istream is(path);
|
||||
expect_equal(is.read(buffer_in, length), length);
|
||||
expect_equal(std::string_view(test_str, length), std::string_view(buffer_in, length));
|
||||
}
|
||||
64
libs/io/tests/memory.cpp
Normal file
64
libs/io/tests/memory.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include <psemek/test/test.hpp>
|
||||
|
||||
#include <psemek/io/memory_stream.hpp>
|
||||
|
||||
using namespace psemek::io;
|
||||
|
||||
test_case(io_memory_null)
|
||||
{
|
||||
char buffer[256]{};
|
||||
|
||||
memory_istream is;
|
||||
expect_throw(is.read(buffer, std::size(buffer)), null_istream);
|
||||
|
||||
memory_ostream os;
|
||||
expect_throw(os.write(buffer, std::size(buffer)), null_ostream);
|
||||
}
|
||||
|
||||
test_case(io_memory_write)
|
||||
{
|
||||
char const test_str[] = "Hello, world!";
|
||||
auto const length = std::size(test_str);
|
||||
char buffer_out[256]{};
|
||||
|
||||
memory_ostream os(buffer_out, buffer_out + std::size(buffer_out));
|
||||
expect_equal(os.write(test_str, length), length);
|
||||
expect_equal(std::string_view(test_str, length), std::string_view(buffer_out, length));
|
||||
}
|
||||
|
||||
test_case(io_memory_read)
|
||||
{
|
||||
char const test_str[] = "Hello, world!";
|
||||
auto const length = std::size(test_str);
|
||||
char buffer_in[256]{};
|
||||
|
||||
memory_istream is(test_str, test_str + length);
|
||||
expect_equal(is.read(buffer_in, length), length);
|
||||
expect_equal(std::string_view(test_str, length), std::string_view(buffer_in, length));
|
||||
}
|
||||
|
||||
test_case(io_memory_write__overflow)
|
||||
{
|
||||
char const test_str[] = "Hello, world!";
|
||||
char buffer_out[8]{};
|
||||
auto const length = std::size(buffer_out);
|
||||
|
||||
expect_less(length, std::size(test_str));
|
||||
|
||||
memory_ostream os(buffer_out, buffer_out + length);
|
||||
expect_equal(os.write(test_str, std::size(test_str)), length);
|
||||
expect_equal(std::string_view(test_str, length), std::string_view(buffer_out, length));
|
||||
}
|
||||
|
||||
test_case(io_memory_read__overflow)
|
||||
{
|
||||
char const test_str[] = "Hello, world!";
|
||||
char buffer_in[8]{};
|
||||
auto const length = std::size(buffer_in);
|
||||
|
||||
expect_less(length, std::size(test_str));
|
||||
|
||||
memory_istream is(test_str, test_str + std::size(test_str));
|
||||
expect_equal(is.read(buffer_in, length), length);
|
||||
expect_equal(std::string_view(test_str, length), std::string_view(buffer_in, length));
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue