Skip to content

Commit

Permalink
Add an experimental writer API
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaut committed Aug 17, 2024
1 parent 020af72 commit b096a5e
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 14 deletions.
37 changes: 23 additions & 14 deletions include/fmt/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -1069,32 +1069,41 @@ template <typename T> class iterator_buffer<T*, T> : public buffer<T> {
auto out() -> T* { return &*this->end(); }
};

template <typename Container>
class container_buffer : public buffer<typename Container::value_type> {
private:
using value_type = typename Container::value_type;

static FMT_CONSTEXPR void grow(buffer<value_type>& buf, size_t capacity) {
auto& self = static_cast<container_buffer&>(buf);
self.container.resize(capacity);
self.set(&self.container[0], capacity);
}

public:
Container& container;

explicit container_buffer(Container& c)
: buffer<value_type>(grow, c.size()), container(c) {}
};

// A buffer that writes to a container with the contiguous storage.
template <typename OutputIt>
class iterator_buffer<
OutputIt,
enable_if_t<detail::is_back_insert_iterator<OutputIt>::value &&
is_contiguous<typename OutputIt::container_type>::value,
typename OutputIt::container_type::value_type>>
: public buffer<typename OutputIt::container_type::value_type> {
: public container_buffer<typename OutputIt::container_type> {
private:
using container_type = typename OutputIt::container_type;
using value_type = typename container_type::value_type;
container_type& container_;

static FMT_CONSTEXPR void grow(buffer<value_type>& buf, size_t capacity) {
auto& self = static_cast<iterator_buffer&>(buf);
self.container_.resize(capacity);
self.set(&self.container_[0], capacity);
}
using base = container_buffer<typename OutputIt::container_type>;

public:
explicit iterator_buffer(container_type& c)
: buffer<value_type>(grow, c.size()), container_(c) {}
explicit iterator_buffer(typename OutputIt::container_type& c) : base(c) {}
explicit iterator_buffer(OutputIt out, size_t = 0)
: iterator_buffer(get_container(out)) {}
: base(get_container(out)) {}

auto out() -> OutputIt { return OutputIt(container_); }
auto out() -> OutputIt { return OutputIt(this->container); }
};

// A buffer that counts the number of code units written discarding the output.
Expand Down
36 changes: 36 additions & 0 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,42 @@ class basic_memory_buffer : public detail::buffer<T> {

using memory_buffer = basic_memory_buffer<char>;

// A writer to a buffered stream. It doesn't own the underlying stream.
class writer {
private:
detail::buffer<char>* buf_;

// We cannot create a file buffer in advance because any write to a FILE may
// invalidate it.
FILE* file_;

public:
writer(FILE* f) : buf_(nullptr), file_(f) {}
writer(detail::buffer<char>& buf) : buf_(&buf) {}

/// Formats `args` according to specifications in `fmt` and writes the
/// output to the file.
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
if (buf_)
fmt::format_to(appender(*buf_), fmt, std::forward<T>(args)...);
else
fmt::print(file_, fmt, std::forward<T>(args)...);
}
};

class string_buffer {
private:
detail::container_buffer<std::string> buf_;
std::string str_;

public:
string_buffer() : buf_(str_) {}

operator writer() { return writer(buf_); }

std::string& str() { return str_; }
};

template <typename T, size_t SIZE, typename Allocator>
struct is_contiguous<basic_memory_buffer<T, SIZE, Allocator>> : std::true_type {
};
Expand Down
2 changes: 2 additions & 0 deletions include/fmt/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,8 @@ class FMT_API ostream : private detail::buffer<char> {
ostream(ostream&& other) noexcept;
~ostream();

operator writer() { return writer(*this); }

void flush() {
if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0]));
Expand Down
23 changes: 23 additions & 0 deletions test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2478,3 +2478,26 @@ FMT_END_NAMESPACE
TEST(format_test, ustring) {
EXPECT_EQ(fmt::format("{}", ustring()), "ustring");
}

TEST(format_test, writer) {
EXPECT_WRITE(
stdout,
{
auto w = fmt::writer(stdout);
w.print("{}", 42);
},
"42");

auto pipe = fmt::pipe();
auto write_end = pipe.write_end.fdopen("w");
fmt::writer(write_end.get()).print("42");
write_end.close();
auto read_end = pipe.read_end.fdopen("r");
int n = 0;
int result = fscanf(read_end.get(), "%d", &n);
EXPECT_EQ(n, 42);

auto s = fmt::string_buffer();
fmt::writer(s).print("foo");
EXPECT_EQ(s.str(), "foo");
}

0 comments on commit b096a5e

Please sign in to comment.