/* *****************************************************************************
* kblib is a general utility library for C++14 and C++17, intended to provide
* performant high-level abstractions and more expressive ways to do simple
* things.
*
* Copyright (c) 2021 killerbee
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
* ****************************************************************************/
/**
* @file
* @brief Provides I/O utilities.
*
* @author killerbee
* @date 2019-2021
* @copyright GNU General Public Licence v3.0
*/
#ifndef KBLIB_IO_H
#define KBLIB_IO_H
#include "fakestd.h"
#include "traits.h"
#include
#include
#include
#include
#if KBLIB_USE_CXX17
# include
# include
# include
# if ! defined(_WIN32) \
&& (defined(__unix__) || defined(__unix) \
|| (defined(__APPLE__) && defined(__MACH__)))
/* UNIX-style OS. ------------------------------------------- */
# include
# if defined(_POSIX_VERSION)
# define KBLIB_POSIX_TMPFILE
# endif
# endif
#endif
#include
namespace KBLIB_NS {
template , int> = 0>
auto get_contents(std::istream& in, D& out) -> auto {
in.seekg(0, std::ios::end);
auto size = in.tellg();
out.resize(static_cast(size));
in.seekg(0, std::ios::beg);
in.read(reinterpret_cast(out.data()), size);
return size;
}
template , int> = 0>
auto get_contents(std::istream& in, D& out) -> auto {
in.seekg(0, std::ios::end);
auto size = in.tellg();
out.resize(static_cast(size));
in.seekg(0, std::ios::beg);
std::copy((std::istreambuf_iterator(in)),
std::istreambuf_iterator(), out.begin());
return size;
}
#if KBLIB_USE_CXX17
/**
* @brief Read the entire contents of a file into a container, such as
* std::string or std::vector. Note that it will be most efficient to read
* into contiguous containers, as opposed to non-contiguous containers.
*
* @param filename The filename to open.
* @tparam D A contiguous sequence container, which will be created and filled
* with the contents of the file to be read.
* @return std::optional The contents of the file, if reading was successful.
*/
template
auto get_file_contents(const string& filename) -> std::optional {
static_assert(std::is_trivially_copyable_v,
"D must be a sequence of trivial types");
static_assert(sizeof(typename D::value_type) == 1,
"D must be a sequence of char-sized objects.");
std::optional out;
if (std::ifstream in(filename, std::ios::in | std::ios::binary); in) {
const auto fsize = get_contents(in, out.emplace());
if (fsize != to_signed(out->size())) {
}
}
return out;
}
#endif
/**
* @brief Read the entire contents of a file into a container, such as
* std::string or std::vector. Note that it will be most efficient to read
* into contiguous containers, as opposed to non-contiguous containers.
*
* @param filename The filename to open.
* @tparam D A contiguous sequence container, which will be created and filled
* with the contents of the file to be read.
* @return std::optional The contents of the file, if reading was successful.
*/
template
auto try_get_file_contents(const string& filename) -> D {
static_assert(std::is_trivially_copyable::value,
"D must be a sequence of trivial types");
static_assert(sizeof(typename D::value_type) == 1,
"D must be a sequence of char-sized objects.");
D out;
std::ifstream in(filename, std::ios::in | std::ios::binary);
if (in) {
in.exceptions(std::ios_base::failbit | std::ios_base::badbit);
get_contents(in, out);
} else {
throw std::system_error(std::make_error_code(std::errc::io_error),
"could not open file " + std::string(filename));
}
return out;
}
/**
* @brief By-value std::getline wrapper.
*
* @param is The stream to extract from.
* @return std::string A single line of text from the stream.
*/
inline auto getline(std::istream& is) -> std::string {
std::string ret;
std::getline(is, ret);
return ret;
}
/**
* @brief Consume all non-spaces to first break, then eat that, too.
*
* @param is
* @return std::istream
*/
inline auto eat_word(std::istream& is) -> std::istream& {
do {
is.get();
} while (is and not std::isspace(is.peek()));
return is;
}
/**
* @brief Eat spaces, don't eat an extra.
*
* @deprecated Use std::ws instead.
*
* @param is
* @return std::istream
*/
[[deprecated("Use std::ws instead")]] inline std::istream& eat_space(
std::istream& is) {
while (is and std::isspace(is.peek())) {
is.get();
}
return is;
}
/**
* @brief A helper class for wrapping stream manipulators.
*/
template
struct get_manip {
F _f;
};
/**
* @brief Read in spaces until the end of the line is found.
*
* nl may be used to consume whitespace left over after a formatted
* input operation before doing an unformatted input operation (such as
* std::getline).
*
* @remark Example usage:
* @code
* int x{};
* std::cout << "Enter a number: ";
* std::cin >> x; // potentially leaves a new line in the stream
* std::cout << "Got " << x << '\n';
* std::string str;
* std::cout << "Enter a line of text: ";
* std::getline(std::cin >> kblib::nl, str);
* std::cout << "Got " << std::quoted(str) << '\n';
* @endcode
*
* @param is The stream to read from.
* @return std::istream& is.
*/
template
auto nl(std::basic_istream& is)
-> std::basic_istream& {
auto n = static_cast(is.widen('\n'));
for (typename Traits::int_type c = is.peek();
is and c != Traits::eof()
and std::isspace(static_cast(c), is.getloc()) and c != n;
c = is.peek()) {
is.ignore();
}
if (is.peek() == n) {
is.ignore();
}
return is;
}
template
struct unicode_widen : std::false_type {};
template <>
struct unicode_widen : std::true_type {};
#if KBLIB_USE_CHAR8_T
template <>
struct unicode_widen : std::true_type {};
template <>
struct unicode_widen : std::true_type {};
#endif
#if KBLIB_CHAR_IS_UTF8
template <>
struct unicode_widen : std::true_type {};
template <>
struct unicode_widen : std::true_type {};
#endif
template
constexpr static bool unicode_widen_v = unicode_widen::value;
/**
* @brief Read a character from an input stream only if it equals c. Acts as an
* UnformattedInputOperation, that is, it will not ignore any leading
* whitespace.
*/
template
auto unformatted_expect(CharT c) -> auto {
auto _f = [c](auto& istream) -> decltype(istream) {
using SCharT = typename std::decay_t::char_type;
#if KBLIB_USE_CHAR8_t
// clang-format off
static_assert(
std::is_same_v
or (not std::is_same_v
and not std::is_same_v),
"No support for char8_t conversions.");
// clang-format on
#endif
auto widen_equal = [&](auto di) {
if (di == std::decay_t::traits_type::eof()) {
return false;
}
auto d = static_cast(di);
// Feasible:
// T -> T : c == d
// T -> char : istream.widen(c) == d
// u32 -> u16 : c == d
// Not currently feasible:
// SCharT == char : read multiple chars and convert to CharT?
// ... : convert between different wide char types?
static_assert(
(std::is_same::value
or std::is_same::value),
"Stream character type incompatible with argument type.");
#if KBLIB_USE_CXX17
# define IF_CONSTEXPR constexpr
#else
# define IF_CONSTEXPR
#endif
if IF_CONSTEXPR (std::is_same::value) {
return c == d;
} else if IF_CONSTEXPR (unicode_widen_v) {
return c == d;
} else {
return istream.widen(c) == d;
}
#undef IF_CONSTEXPR
};
if (widen_equal(istream.peek())) {
void(istream.get());
} else {
istream.setstate(std::ios_base::failbit);
}
return istream;
};
return get_manip{_f};
}
/**
* @brief Read a character from an input stream only if it equals c. Acts as a
* FormattedInputOperation, that is, leading whitespace is ignored.
*/
template
auto expect(CharT c) -> auto {
auto _f = [c](auto& istream) -> decltype(istream) {
return istream >> std::ws >> unformatted_expect(c);
};
return get_manip{_f};
}
/**
* @brief Read a whole line into a std::basic_string-like class template.
*
* When used like
* os >> get_line(str);
* for a std::ostream& os and std::string str, reads a full line into str
* instead of just a single word.
*
* @param str The string to read a line into.
*/
template class string>
inline auto get_line(string& str) -> auto {
auto _f = [&](auto& istream) -> decltype(istream) {
std::getline(istream, str);
return istream;
};
return get_manip{_f};
}
/**
* @brief Read a delimited string into a std::basic_string-like class template.
*
* When used like
* os >> get_line(str, '\n');
* for a std::ostream& os and std::string str, reads a full line into str
* instead of just a single word.
*
* @param str The string to read into.
* @param delim The delimiter at which to stop reading text.
*/
template class string>
inline auto get_line(string& str, CharT delim) -> auto {
auto _f = [&, delim](auto& istream) -> decltype(istream) {
std::getline(istream, str, delim);
return istream;
};
return get_manip{_f};
}
/**
* @brief Actually calls the manipulator.
*/
template
std::basic_istream& operator>>(std::basic_istream& is,
get_manip func) {
return func._f(is);
}
/**
* @brief Actually calls the manipulator.
*/
template
std::basic_ostream& operator<<(std::basic_ostream& is,
get_manip func) {
return func._f(is);
}
/**
* @namespace detail_io
* @internal
*/
namespace detail_io {
/*
class steambuf_template : public std::streambuf {
protected:
auto imbue(const std::locale& loc) -> void override;
auto setbuf(char_type* s, std::streamsize n) -> std::streambuf* override;
auto seekoff(off_type off, std::ios_base::seekdir dir,
std::ios_base::openmode which) -> pos_type override; auto seekpos(pos_type
pos, std::ios_base::openmode which) -> pos_type override; auto sync() -> int
override;
auto showmanyc() -> std::streamsize override;
auto underflow() -> int_type override;
auto uflow() -> int_type override;
auto xsgetn(char_type* s, std::streamsize count) -> std::streamsize
override;
auto xsputn(const char_type* s, std::streamsize count) -> std::streamsize
override; auto overflow(int_type ch) -> int_type override;
auto pbackfail(int_type c) -> int_type override;
};
*/
template
class basic_teestreambuf
: public std::basic_streambuf {
public:
using base_type = std::basic_streambuf;
static_assert(std::is_same::value,
"Backing streams must be compatible.");
static_assert(std::is_same::value,
"Backing streams must be compatible.");
using typename base_type::char_type;
using typename base_type::traits_type;
using typename base_type::int_type;
using typename base_type::off_type;
using typename base_type::pos_type;
basic_teestreambuf() = delete;
basic_teestreambuf(SB1_t* a, SB2_t* b)
: a(a)
, b(b) {
this->setp(nullptr, nullptr);
}
private:
auto bool_to_failure(bool B) const noexcept -> int_type {
return B ? traits_type::to_int_type(char_type{}) : traits_type::eof();
}
protected:
auto imbue(const std::locale& loc) -> void override {
a->pubimbue(loc);
b->pubimbue(loc);
return;
}
auto sync() -> int override { return a->pubsync() | b->pubsync(); }
auto xsputn(const char_type* s, std::streamsize count)
-> std::streamsize override {
auto a_ct = a->sputn(s, count);
auto b_ct = b->sputn(s, count);
std::streamsize successful = std::min(a_ct, b_ct);
if (successful == count) {
return count;
} else {
return 0;
}
}
auto overflow(int_type ch) -> int_type override {
if (not traits_type::eq_int_type(ch, traits_type::eof())) {
auto r_a = a->sputc(traits_type::to_char_type(ch));
auto r_b = b->sputc(traits_type::to_char_type(ch));
if (traits_type::not_eof(r_a) and traits_type::not_eof(r_b)) {
return traits_type::to_int_type({});
} else {
return traits_type::eof();
}
}
return traits_type::to_int_type({});
}
private:
SB1_t* a;
SB2_t* b;
};
template
using buf_for
= std::remove_pointer_t().rdbuf())>;
} // namespace detail_io
template
class basic_teestream
: public std::basic_ostream {
private:
using buf_type = detail_io::basic_teestreambuf,
detail_io::buf_for>;
buf_type buf;
using ostream_type = std::basic_ostream;
public:
using typename ostream_type::char_type;
using typename ostream_type::traits_type;
using typename ostream_type::int_type;
using typename ostream_type::off_type;
using typename ostream_type::pos_type;
basic_teestream(StreamA& a, StreamB& b)
: ostream_type(&buf)
, buf(a.rdbuf(), b.rdbuf()) {}
auto rdbuf() const -> buf_type* { return &buf; }
};
#if 1 || KBLIB_USE_CXX17
template
auto tee(StreamA& a, StreamB& b) -> basic_teestream {
return {a, b};
}
#endif
#if KBLIB_USE_CXX17
template ,
typename P = typename D::pointer>
struct file_deleter {
std::filesystem::path path;
using pointer = P;
void operator()(P fs) {
static_cast(this)(fs);
std::filesystem::remove(path);
}
};
# ifdef KBLIB_POSIX_TMPFILE
namespace detail_io {
struct fd_closer {
void operator()(int fd) const noexcept { close(fd); }
using pointer = int;
};
} // namespace detail_io
using fd_deleter = file_deleter;
# endif
template
[[nodiscard]] auto scoped_file(const std::filesystem::path& path,
std::ios_base::openmode mode
= std::ios_base::in | std::ios_base::out) {
return std::unique_ptr>{
new std::fstream{path, mode}, {path}};
}
template
[[nodiscard]] auto tmpfile(const std::filesystem::path& path,
std::ios_base::openmode mode
= std::ios_base::in | std::ios_base::out) {
# ifdef KBLIB_POSIX_TMPFILE
auto p = std::make_unique(path, mode);
std::filesystem::remove(path);
return p;
# else
return scoped_file(path, mode);
# endif
}
#endif
} // namespace KBLIB_NS
#endif // KBLIB_IO_H