diff --git a/include/fmt/os.h b/include/fmt/os.h index 31df8eea..a9517ef8 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -277,7 +277,8 @@ class file { enum { RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. - RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. + RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing. + CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist. }; // Constructs a file object which doesn't represent any file. @@ -341,6 +342,63 @@ class file { // Returns the memory page size. long getpagesize(); + +class direct_buffered_file; + +template +void print(direct_buffered_file& f, const S& format_str, + const Args&... args); + +// A buffered file with a direct buffer access and no synchronization. +class direct_buffered_file { + private: + file file_; + + enum { buffer_size = 4096 }; + char buffer_[buffer_size]; + int pos_; + + void flush() { + if (pos_ == 0) return; + file_.write(buffer_, pos_); + pos_ = 0; + } + + int free_capacity() const { return buffer_size - pos_; } + + public: + direct_buffered_file(cstring_view path, int oflag) + : file_(path, oflag), pos_(0) {} + + ~direct_buffered_file() { + flush(); + } + + void close() { + flush(); + file_.close(); + } + + template + friend void print(direct_buffered_file& f, const S& format_str, + const Args&... args) { + // We could avoid double buffering. + auto buf = fmt::memory_buffer(); + fmt::format_to(std::back_inserter(buf), format_str, args...); + auto remaining_pos = 0; + auto remaining_size = buf.size(); + while (remaining_size > detail::to_unsigned(f.free_capacity())) { + auto size = f.free_capacity(); + memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size); + f.pos_ += size; + f.flush(); + remaining_pos += size; + remaining_size -= size; + } + memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size); + f.pos_ += static_cast(remaining_size); + } +}; #endif // FMT_USE_FCNTL #ifdef FMT_LOCALE diff --git a/test/gtest-extra.h b/test/gtest-extra.h index a24efb4a..3ed8052b 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -141,7 +141,8 @@ class SuppressAssert { std::string read(fmt::file& f, size_t count); # define EXPECT_READ(file, expected_content) \ - EXPECT_EQ(expected_content, read(file, std::strlen(expected_content))) + EXPECT_EQ(expected_content, \ + read(file, fmt::string_view(expected_content).size())) #else # define EXPECT_WRITE(file, statement, expected_output) SUCCEED() diff --git a/test/os-test.cc b/test/os-test.cc index d1530e5a..186198ef 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -287,6 +287,26 @@ TEST(BufferedFileTest, Fileno) { EXPECT_READ(copy, FILE_CONTENT); } +TEST(DirectBufferedFileTest, Print) { + fmt::direct_buffered_file out( + "test-file", fmt::file::WRONLY | fmt::file::CREATE); + fmt::print(out, "The answer is {}.\n", 42); + out.close(); + file in("test-file", file::RDONLY); + EXPECT_READ(in, "The answer is 42.\n"); +} + +TEST(DirectBufferedFileTest, BufferBoundary) { + auto str = std::string(4096, 'x'); + fmt::direct_buffered_file out( + "test-file", fmt::file::WRONLY | fmt::file::CREATE); + fmt::print(out, "{}", str); + fmt::print(out, "{}", str); + out.close(); + file in("test-file", file::RDONLY); + EXPECT_READ(in, str + str); +} + TEST(FileTest, DefaultCtor) { file f; EXPECT_EQ(-1, f.descriptor());