Add simple binary io library

This commit is contained in:
Nikita Lisitsa 2021-04-19 20:59:49 +03:00
parent 401e0a29e2
commit 22ef7b45a8
11 changed files with 548 additions and 0 deletions

7
libs/io/CMakeLists.txt Normal file
View 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)

View 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;
};
}

View 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;
};
}

View 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;
};
}

View 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
View 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";
}
}

View 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_);
}
}

View 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
View 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
View 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
View 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));
}