/* SPDX-License-Identifier: MIT */ #ifndef RGBDS_FILE_HPP #define RGBDS_FILE_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include "helpers.hpp" #include "platform.hpp" #include "gfx/main.hpp" // Convenience feature for visiting the below. template struct Visitor : Ts... { using Ts::operator()...; }; template Visitor(Ts...) -> Visitor; class File { // Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`. std::variant _file; public: File() {} ~File() { close(); } /** * This should only be called once, and before doing any `->` operations. * Returns `nullptr` on error, and a non-null pointer otherwise. */ File *open(std::filesystem::path const &path, std::ios_base::openmode mode) { if (path != "-") { return _file.emplace().open(path, mode) ? this : nullptr; } else if (mode & std::ios_base::in) { assert(!(mode & std::ios_base::out)); _file.emplace(std::cin.rdbuf()); if (setmode(STDIN_FILENO, mode & std::ios_base::binary ? O_BINARY : O_TEXT) == -1) { fatal("Failed to set stdin to %s mode: %s", mode & std::ios_base::binary ? "binary" : "text", strerror(errno)); } } else { assert(mode & std::ios_base::out); _file.emplace(std::cout.rdbuf()); } return this; } std::streambuf &operator*() { return std::visit(Visitor{[](std::filebuf &file) -> std::streambuf & { return file; }, [](std::streambuf *buf) -> std::streambuf & { return *buf; }}, _file); } std::streambuf const &operator*() const { // The non-`const` version does not perform any modifications, so it's okay. return **const_cast(this); } std::streambuf *operator->() { return &**this; } std::streambuf const *operator->() const { // See the `operator*` equivalent. return const_cast(this)->operator->(); } File *close() { return std::visit(Visitor{[this](std::filebuf &file) { // This is called by the destructor, and an explicit `close` // shouldn't close twice. _file.emplace(nullptr); return file.close() != nullptr; }, [](std::streambuf *buf) { return buf != nullptr; }}, _file) ? this : nullptr; } char const *c_str(std::filesystem::path const &path) const { // FIXME: This is a hack to prevent the path string from being destroyed until // `.c_str(path)` is called again. It's necessary because just `return path.c_str()` // fails on Windows, where paths use `wchar_t`. static std::string path_string; return std::visit(Visitor{[&path](std::filebuf const &) { path_string = path.string(); return path_string.c_str(); }, [](std::streambuf const *buf) { return buf == std::cin.rdbuf() ? "" : ""; }}, _file); } }; #endif // RGBDS_FILE_HPP