2017-04-09 14:09:54 +00:00
|
|
|
/** @addtogroup Streams
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @file io.hh
|
|
|
|
*
|
|
|
|
* @brief File streams and standard output/input/error manipulation.
|
2015-06-30 18:25:40 +00:00
|
|
|
*
|
2017-04-09 14:09:54 +00:00
|
|
|
* This file implements a file stream structure equivalent to the C `FILE`
|
|
|
|
* as well as wrappers over standard input/output/error and global functions
|
|
|
|
* for formatted writing into standard output.
|
|
|
|
*
|
|
|
|
* @copyright See COPYING.md in the project tree for further information.
|
2015-06-30 18:25:40 +00:00
|
|
|
*/
|
|
|
|
|
2015-07-13 19:08:55 +00:00
|
|
|
#ifndef OSTD_IO_HH
|
|
|
|
#define OSTD_IO_HH
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2017-04-09 14:44:45 +00:00
|
|
|
#include <cstddef>
|
2017-03-09 18:21:01 +00:00
|
|
|
#include <cstdio>
|
2017-03-02 17:12:00 +00:00
|
|
|
#include <cerrno>
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2015-07-13 19:08:55 +00:00
|
|
|
#include "ostd/platform.hh"
|
|
|
|
#include "ostd/string.hh"
|
|
|
|
#include "ostd/stream.hh"
|
|
|
|
#include "ostd/format.hh"
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2015-07-13 19:07:14 +00:00
|
|
|
namespace ostd {
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @addtogroup Streams
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @brief The mode to open file streams with.
|
|
|
|
*
|
|
|
|
* Libostd file streams are always opened in binary mode. Text mode is not
|
|
|
|
* directly supported (the only way to get it is to encapsulate a C `FILE *`
|
|
|
|
* that is already opened in text mode).
|
|
|
|
*
|
|
|
|
* See the C fopen() function documentation for more info on modes.
|
|
|
|
*/
|
2017-02-16 19:39:05 +00:00
|
|
|
enum class stream_mode {
|
2017-04-09 14:09:54 +00:00
|
|
|
READ = 0, ///< Reading, equivalent to the C `rb` mode.
|
|
|
|
WRITE, ///< Writing, equivalent to the C `wb` mode.
|
|
|
|
APPEND, ///< Appending, equivalent to the C `ab` mode.
|
|
|
|
READ_U, ///< Read/update, equivalent to the C `rb+` mode.
|
|
|
|
WRITE_U, ///< Write/update, equivalent to the C `wb+` mode.
|
|
|
|
APPEND_U ///< Append/update, equivalent to the C `ab+` mode.
|
2015-06-30 18:25:40 +00:00
|
|
|
};
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief A file stream.
|
|
|
|
*
|
|
|
|
* File streams are equivalent to the C `FILE` type. You can open new file
|
|
|
|
* streams and you can also create high level file stream over C file streams.
|
|
|
|
* File streams are seekable except in special cases (stdin/stdout/stderr).
|
|
|
|
*
|
|
|
|
* File streams implement a concept of ownership; if they own the underlying
|
|
|
|
* stream, which is every time when the path-based constructor or open() are
|
|
|
|
* used, they close the underlying stream on destruction (if still open).
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
struct OSTD_EXPORT file_stream: stream {
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Crates an empty file stream.
|
|
|
|
*
|
|
|
|
* The resulting file stream won't have an associated file. Any operations
|
|
|
|
* involving the potential associated file are considered unfedined.
|
|
|
|
*/
|
2017-02-16 19:39:05 +00:00
|
|
|
file_stream(): p_f(), p_owned(false) {}
|
2017-04-09 14:09:54 +00:00
|
|
|
|
|
|
|
/** @brief File streams are not copy constructible. */
|
2017-02-16 19:39:05 +00:00
|
|
|
file_stream(file_stream const &) = delete;
|
2017-04-09 14:09:54 +00:00
|
|
|
|
|
|
|
/** @brief Creates a file stream by moving.
|
|
|
|
*
|
|
|
|
* The other file stream is set to an empty state,
|
|
|
|
* i.e. it will not have any associated file set.
|
|
|
|
*/
|
2017-02-16 19:39:05 +00:00
|
|
|
file_stream(file_stream &&s): p_f(s.p_f), p_owned(s.p_owned) {
|
2015-06-30 18:25:40 +00:00
|
|
|
s.p_f = nullptr;
|
|
|
|
s.p_owned = false;
|
|
|
|
}
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Creates a file stream using a file path.
|
|
|
|
*
|
|
|
|
* The path is a relative or absolute path, basically anything that
|
|
|
|
* can be passed to C fopen(). The path does not need to be null
|
|
|
|
* terminated. The construction might fail, if it does, this will
|
|
|
|
* not throw an error but instead the stream will be left without
|
|
|
|
* an associated state, which you can check for later using is_open().
|
|
|
|
*
|
|
|
|
* It works by calling open(). The default mode (when none is provided)
|
|
|
|
* is a plain reading stream.
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
file_stream(string_range path, stream_mode mode = stream_mode::READ):
|
|
|
|
p_f()
|
|
|
|
{
|
2015-06-30 18:25:40 +00:00
|
|
|
open(path, mode);
|
|
|
|
}
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Creates a file stream using a C `FILE` pointer.
|
|
|
|
*
|
|
|
|
* You can then manipulate the pointer using the stream, but it will
|
|
|
|
* not be owned; you need to manually close it using the correct C
|
|
|
|
* function when you're done.
|
|
|
|
*/
|
2017-02-16 19:39:05 +00:00
|
|
|
file_stream(FILE *f): p_f(f), p_owned(false) {}
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Calls close() on the stream. */
|
2017-02-16 19:39:05 +00:00
|
|
|
~file_stream() { close(); }
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief File streams are not copy assignable. */
|
2017-02-16 19:39:05 +00:00
|
|
|
file_stream &operator=(file_stream const &) = delete;
|
2017-04-09 14:09:54 +00:00
|
|
|
|
|
|
|
/** @brief Assigns another stream to this one by move.
|
|
|
|
*
|
|
|
|
* If we're currently owning another file, close() is called first.
|
|
|
|
* Then the other stream's state is moved here and the other stream
|
|
|
|
* is left empty (as if initialized with a default constructor).
|
|
|
|
*/
|
2017-02-16 19:39:05 +00:00
|
|
|
file_stream &operator=(file_stream &&s) {
|
2015-06-30 18:25:40 +00:00
|
|
|
close();
|
|
|
|
swap(s);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Opens a file stream by file path.
|
|
|
|
*
|
|
|
|
* If there is currently another file associated with the stream,
|
|
|
|
* this just directly returns `false`. Otherwise, it will try to
|
|
|
|
* open the file. If that fails for some reason (path too long or
|
|
|
|
* fopen() failed for some other reason), `false` is returned.
|
|
|
|
* Otherwise, `true` is returned and both is_open() and is_owned()
|
|
|
|
* will be true.
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
bool open(string_range path, stream_mode mode = stream_mode::READ);
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Opens a file stream by C `FILE` pointer.
|
|
|
|
*
|
|
|
|
* This sets the associated file pointer. If there is currently
|
|
|
|
* another file associated with the stream, this just directly
|
|
|
|
* returns `false`. Otherwise, it will set the association and
|
|
|
|
* returns `true`.
|
|
|
|
*
|
|
|
|
* In the end, is_open() will be true but is_owned() will be false.
|
|
|
|
* You need to manually take care of the pointer because this stream
|
|
|
|
* will not close it.
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
bool open(FILE *f);
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Checks if there is a resource associated with this stream. */
|
2015-06-30 18:25:40 +00:00
|
|
|
bool is_open() const { return p_f != nullptr; }
|
2017-04-09 14:09:54 +00:00
|
|
|
|
|
|
|
/** @brief Checks if we're owning the associated resource. */
|
2015-06-30 18:25:40 +00:00
|
|
|
bool is_owned() const { return p_owned; }
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Closes the associated file.
|
|
|
|
*
|
|
|
|
* If both is_open() and is_owned() are true, this will close the
|
|
|
|
* associated file and set the stream to empty. Otherwise, it will
|
|
|
|
* do nothing.
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
void close();
|
2017-04-09 14:09:54 +00:00
|
|
|
|
|
|
|
/** @brief Checks if the associated stream has an end of file set.
|
|
|
|
*
|
|
|
|
* This is not necessarily true if the current stream position is
|
|
|
|
* at the end. It becomes true once you've tried reading past the
|
|
|
|
* end of the file.
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
bool end() const;
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Seeks within the stream.
|
|
|
|
*
|
|
|
|
* File streams are normally seekable. Sometimes they are not
|
|
|
|
* though, such as when this represents an stdin/stdout/stderr.
|
|
|
|
*
|
|
|
|
* @throws ostd::stream_error with errno on failure.
|
|
|
|
*
|
|
|
|
* @see tell()
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
void seek(stream_off_t pos, stream_seek whence = stream_seek::SET);
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Tells the current stream position.
|
|
|
|
*
|
|
|
|
* @throws ostd::stream_error with EIO on failure.
|
|
|
|
*
|
|
|
|
* @see seek()
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
stream_off_t tell() const;
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Flushes the associated stream's buffer.
|
|
|
|
*
|
|
|
|
* @throws ostd::stream_error with errno on failure.
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
void flush();
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Reads at most a number of bytes from the stream.
|
|
|
|
*
|
|
|
|
* If an end-of-file was reached during the reading, this will return
|
|
|
|
* the amount of bytes actually read. If the reading failed somehow,
|
|
|
|
* this will throw an ostd::stream_error with EIO. Otherwise, it should
|
|
|
|
* return `count`.
|
|
|
|
*
|
|
|
|
* @throws ostd::stream_error with EIO on failure (not on EOF).
|
|
|
|
*
|
|
|
|
* @see write_bytes()
|
|
|
|
*/
|
2017-04-09 14:44:45 +00:00
|
|
|
std::size_t read_bytes(void *buf, std::size_t count);
|
2017-04-09 14:09:54 +00:00
|
|
|
|
|
|
|
/** @brief Writes `count` bytes into the stream.
|
|
|
|
*
|
|
|
|
* @throws ostd::stream_error with EIO on failure.
|
|
|
|
*
|
|
|
|
* @see read_bytes()
|
|
|
|
*/
|
2017-04-09 14:44:45 +00:00
|
|
|
void write_bytes(void const *buf, std::size_t count);
|
2015-07-01 19:59:05 +00:00
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Reads a single character from the stream.
|
|
|
|
*
|
|
|
|
* Does not use read_bytes() like the default implementation. Instead,
|
|
|
|
* it uses fgetc() to read the character. If that fails due to a read
|
|
|
|
* error or EOF, this will throw.
|
|
|
|
*
|
|
|
|
* @throws ostd::stream_error with EIO on failure.
|
|
|
|
*
|
|
|
|
* @see put_char()
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
int get_char();
|
2017-04-09 14:09:54 +00:00
|
|
|
|
|
|
|
/** @brief Writes a single character into the stream.
|
|
|
|
*
|
|
|
|
* Does not use write_bytes() like the default implementation. Instead,
|
|
|
|
* it uses fputc() to write the character. If that fails for any reason,
|
|
|
|
* it throws.
|
|
|
|
*
|
|
|
|
* @throws ostd::stream_error with EIO on failure.
|
|
|
|
*
|
|
|
|
* @see get_char()
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
void put_char(int c);
|
2015-07-01 19:59:05 +00:00
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Swaps two file streams including ownership. */
|
2017-02-16 19:39:05 +00:00
|
|
|
void swap(file_stream &s) {
|
2017-01-29 14:56:02 +00:00
|
|
|
using std::swap;
|
|
|
|
swap(p_f, s.p_f);
|
|
|
|
swap(p_owned, s.p_owned);
|
2015-06-30 18:25:40 +00:00
|
|
|
}
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Gets an underlying C `FILE` pointer backing the stream.
|
|
|
|
*
|
|
|
|
* This returns an associated `FILE` pointer (if opened) or a null
|
|
|
|
* pointer (when no resource is associated with this stream).
|
|
|
|
*
|
|
|
|
* Ownership does not matter in this case. If you're getting a pointer
|
|
|
|
* for a file stream that owns it though, make sure not to close it.
|
|
|
|
*/
|
2017-03-09 18:21:01 +00:00
|
|
|
FILE *get_file() const { return p_f; }
|
2015-06-30 18:25:40 +00:00
|
|
|
|
|
|
|
private:
|
|
|
|
FILE *p_f;
|
|
|
|
bool p_owned;
|
|
|
|
};
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Swaps two file streams including ownership. */
|
2017-02-16 19:39:05 +00:00
|
|
|
inline void swap(file_stream &a, file_stream &b) {
|
2017-01-29 14:56:02 +00:00
|
|
|
a.swap(b);
|
|
|
|
}
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Standard input file stream. */
|
2017-03-09 18:21:01 +00:00
|
|
|
OSTD_EXPORT extern file_stream cin;
|
2017-04-09 14:09:54 +00:00
|
|
|
|
|
|
|
/** @brief Standard output file stream. */
|
2017-03-09 18:21:01 +00:00
|
|
|
OSTD_EXPORT extern file_stream cout;
|
2017-04-09 14:09:54 +00:00
|
|
|
|
|
|
|
/** @brief Standard error file stream. */
|
2017-03-09 18:21:01 +00:00
|
|
|
OSTD_EXPORT extern file_stream cerr;
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2017-02-16 19:39:05 +00:00
|
|
|
/* no need to call anything from file_stream, prefer simple calls... */
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2015-07-21 21:21:54 +00:00
|
|
|
namespace detail {
|
2017-02-01 18:29:42 +00:00
|
|
|
/* lightweight output range for direct stdout */
|
2017-02-16 19:39:05 +00:00
|
|
|
struct stdout_range: output_range<stdout_range> {
|
2017-02-16 19:02:55 +00:00
|
|
|
using value_type = char;
|
|
|
|
using reference = char &;
|
2017-04-09 14:44:45 +00:00
|
|
|
using size_type = std::size_t;
|
|
|
|
using difference_type = std::ptrdiff_t;
|
2017-02-13 22:04:02 +00:00
|
|
|
|
2017-02-16 19:39:05 +00:00
|
|
|
stdout_range() {}
|
2017-02-19 15:45:06 +00:00
|
|
|
void put(char c) {
|
2017-03-09 18:21:01 +00:00
|
|
|
if (std::putchar(c) == EOF) {
|
2017-03-02 17:12:00 +00:00
|
|
|
throw stream_error{EIO, std::generic_category()};
|
2017-02-19 15:45:06 +00:00
|
|
|
}
|
2017-02-01 18:29:42 +00:00
|
|
|
}
|
|
|
|
};
|
2017-02-19 17:46:43 +00:00
|
|
|
|
|
|
|
template<typename R>
|
|
|
|
inline void range_put_all(stdout_range &r, R range) {
|
|
|
|
if constexpr(
|
|
|
|
is_contiguous_range<R> &&
|
|
|
|
std::is_same_v<std::remove_const_t<range_value_t<R>>, char>
|
|
|
|
) {
|
2017-03-09 18:21:01 +00:00
|
|
|
if (std::fwrite(range.data(), 1, range.size(), stdout) != range.size()) {
|
2017-03-02 17:12:00 +00:00
|
|
|
throw stream_error{EIO, std::generic_category()};
|
2017-02-19 17:46:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (; !range.empty(); range.pop_front()) {
|
|
|
|
r.put(range.front());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-06-30 18:25:40 +00:00
|
|
|
}
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Writes all given values into standard output.
|
|
|
|
*
|
|
|
|
* Behaves the same as calling ostd::stream::write() on ostd::cout,
|
|
|
|
* but with more convenience.
|
|
|
|
*
|
|
|
|
* @see ostd::writeln(), ostd::writef(), ostd::writefln()
|
|
|
|
*/
|
2017-03-02 19:01:01 +00:00
|
|
|
template<typename ...A>
|
|
|
|
inline void write(A const &...args) {
|
2017-03-04 14:41:14 +00:00
|
|
|
format_spec sp{'s', cout.getloc()};
|
2017-03-02 19:01:01 +00:00
|
|
|
(sp.format_value(detail::stdout_range{}, args), ...);
|
2015-06-30 21:41:01 +00:00
|
|
|
}
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Writes all given values into standard output followed by a newline.
|
|
|
|
*
|
|
|
|
* Behaves the same as calling ostd::stream::writeln() on ostd::cout,
|
|
|
|
* but with more convenience.
|
|
|
|
*
|
|
|
|
* @see ostd::write(), ostd::writef(), ostd::writefln()
|
|
|
|
*/
|
2017-03-02 19:01:01 +00:00
|
|
|
template<typename ...A>
|
|
|
|
inline void writeln(A const &...args) {
|
2015-06-30 21:41:01 +00:00
|
|
|
write(args...);
|
2017-03-09 18:21:01 +00:00
|
|
|
if (std::putchar('\n') == EOF) {
|
2017-03-02 17:12:00 +00:00
|
|
|
throw stream_error{EIO, std::generic_category()};
|
2017-02-11 00:28:14 +00:00
|
|
|
}
|
2015-06-30 21:41:01 +00:00
|
|
|
}
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Writes a formatted string into standard output.
|
|
|
|
*
|
|
|
|
* Behaves the same as calling ostd::stream::writef() on ostd::cout,
|
|
|
|
* but with more convenience.
|
|
|
|
*
|
|
|
|
* @see ostd::writefln(), ostd::write(), ostd::writeln()
|
|
|
|
*/
|
2015-07-01 01:22:42 +00:00
|
|
|
template<typename ...A>
|
2017-02-16 17:48:14 +00:00
|
|
|
inline void writef(string_range fmt, A const &...args) {
|
2017-03-04 14:41:14 +00:00
|
|
|
format_spec sp{fmt, cout.getloc()};
|
2017-02-26 01:06:02 +00:00
|
|
|
sp.format(detail::stdout_range{}, args...);
|
2015-07-01 01:22:42 +00:00
|
|
|
}
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @brief Writes a formatted string into standard output followed by a newline.
|
|
|
|
*
|
|
|
|
* Behaves the same as calling ostd::stream::writefln() on ostd::cout,
|
|
|
|
* but with more convenience.
|
|
|
|
*
|
|
|
|
* @see ostd::writef(), ostd::write(), ostd::writeln()
|
|
|
|
*/
|
2015-07-01 01:22:42 +00:00
|
|
|
template<typename ...A>
|
2017-02-16 17:48:14 +00:00
|
|
|
inline void writefln(string_range fmt, A const &...args) {
|
2015-07-01 17:51:39 +00:00
|
|
|
writef(fmt, args...);
|
2017-03-09 18:21:01 +00:00
|
|
|
if (std::putchar('\n') == EOF) {
|
2017-03-02 17:12:00 +00:00
|
|
|
throw stream_error{EIO, std::generic_category()};
|
2017-02-11 00:28:14 +00:00
|
|
|
}
|
2015-07-01 01:22:42 +00:00
|
|
|
}
|
|
|
|
|
2017-04-09 14:09:54 +00:00
|
|
|
/** @} */
|
|
|
|
|
2015-07-13 19:07:14 +00:00
|
|
|
} /* namespace ostd */
|
2015-06-30 18:25:40 +00:00
|
|
|
|
2016-02-07 21:17:15 +00:00
|
|
|
#endif
|
2017-04-09 14:09:54 +00:00
|
|
|
|
|
|
|
/** @} */
|