diff --git a/README.rst b/README.rst index 1d8df735..ce74ba98 100644 --- a/README.rst +++ b/README.rst @@ -46,6 +46,24 @@ An object of any user-defined type for which there is an overloaded std::string s = str(fmt::Format("The date is {0}") << Date(2012, 12, 9)); // s == "The date is 2012-12-9" +You can use ``fmt::ActiveFormatter`` to create your own functions +similar to ``fmt::Format`` and ``fmt::Print`` with an arbitrary action +performed when formatting is complete: + + struct PrintError { + void operator()(const fmt::Formatter &f) const { + std::cerr << "Error: " << f.str() << std::endl; + } + }; + + // Formats an error message and prints it to std::cerr. + fmt::ActiveFormatter ReportError(const char *format) { + fmt::ActiveFormatter af(format); + return af; + } + + ReportError("File not found: {0}") << path; + Format string syntax -------------------- diff --git a/format.cc b/format.cc index 1e1feda7..468dd133 100644 --- a/format.cc +++ b/format.cc @@ -366,8 +366,3 @@ void fmt::Formatter::DoFormat() { buffer_.append(start, s + 1); buffer_.resize(buffer_.size() - 1); // Don't count the terminating zero. } - -fmt::BasicArgFormatter::~BasicArgFormatter() { - if (!formatter_) return; - FinishFormatting(); -} diff --git a/format.h b/format.h index d4b67b4e..46cce6f1 100644 --- a/format.h +++ b/format.h @@ -94,7 +94,7 @@ class FormatError : public std::runtime_error { FormatError(const std::string &message) : std::runtime_error(message) {} }; -class BasicArgFormatter; +class ArgFormatter; // Formatter provides string formatting functionality similar to Python's // str.format. The output is stored in a memory buffer that grows dynamically. @@ -183,17 +183,17 @@ class Formatter { format(&Formatter::FormatCustomArg) {} ~Arg() { - // Format is called here to make sure that the argument object - // referred to is still alive, for example: + // Format is called here to make sure that a referred object is + // still alive, for example: // // Print("{0}") << std::string("test"); // // Here an Arg object refers to a temporary std::string which is // destroyed at the end of the statement. Since the string object is // constructed before the Arg object, it will be destroyed after, - // so it will be alive in the Arg's destructor when Format is called. + // so it will be alive in the Arg's destructor where Format is called. // Note that the string object will not necessarily be alive when - // the destructor of BasicArgFormatter is called. + // the destructor of ArgFormatter is called. formatter->Format(); } }; @@ -203,7 +203,7 @@ class Formatter { const char *format_; // Format string. - friend class BasicArgFormatter; + friend class ArgFormatter; void Add(const Arg &arg) { args_.push_back(&arg); @@ -238,36 +238,38 @@ class Formatter { } public: - Formatter() : format_(0) {} + Formatter() : format_(0) { buffer_[0] = 0; } // Formats a string appending the output to the internal buffer. - // Arguments are accepted through the returned BasicArgFormatter object + // Arguments are accepted through the returned ArgFormatter object // using inserter operator<<. - BasicArgFormatter operator()(const char *format); + ArgFormatter operator()(const char *format); std::size_t size() const { return buffer_.size(); } const char *data() const { return &buffer_[0]; } const char *c_str() const { return &buffer_[0]; } + + std::string str() const { return std::string(&buffer_[0], buffer_.size()); } }; // Argument formatter. This is a transient object that normally exists // only as a temporary returned by one of the formatting functions. -// It stores a reference to a formatter and provides operators << -// that feed arguments to the formatter. -class BasicArgFormatter { +// It stores a reference to a formatter and provides operator<< +// that feeds arguments to the formatter. +class ArgFormatter { private: friend class Formatter; protected: mutable Formatter *formatter_; - BasicArgFormatter(BasicArgFormatter& other) + ArgFormatter(ArgFormatter& other) : formatter_(other.formatter_) { other.formatter_ = 0; } - BasicArgFormatter& operator=(const BasicArgFormatter& other) { + ArgFormatter& operator=(const ArgFormatter& other) { formatter_ = other.formatter_; other.formatter_ = 0; return *this; @@ -283,33 +285,24 @@ class BasicArgFormatter { } public: - explicit BasicArgFormatter(Formatter &f) : formatter_(&f) {} - ~BasicArgFormatter(); + explicit ArgFormatter(Formatter &f) : formatter_(&f) {} + ~ArgFormatter() { FinishFormatting(); } - BasicArgFormatter &operator<<(const Formatter::Arg &arg) { + // Feeds an argument to a formatter. + ArgFormatter &operator<<(const Formatter::Arg &arg) { arg.formatter = formatter_; formatter_->Add(arg); return *this; } - friend const char *c_str(const BasicArgFormatter &af) { + // Performs formatting and returns a C string with the output. + friend const char *c_str(const ArgFormatter &af) { return af.FinishFormatting()->c_str(); } - friend std::string str(const BasicArgFormatter &af) { - return af.FinishFormatting()->c_str(); - } -}; - -template -class ArgFormatter : public BasicArgFormatter { - public: - explicit ArgFormatter(Formatter &f) : BasicArgFormatter(f) {} - - ~ArgFormatter() { - if (!formatter_) return; - Callback callback; - callback(*formatter_); + // Performs formatting and returns a std::string with the output. + friend std::string str(const ArgFormatter &af) { + return af.FinishFormatting()->str(); } }; @@ -325,55 +318,53 @@ void Formatter::FormatCustomArg(const void *arg, int width) { std::fill_n(out + str.size(), width - str.size(), ' '); } -inline BasicArgFormatter Formatter::operator()(const char *format) { - BasicArgFormatter formatter(*this); +inline ArgFormatter Formatter::operator()(const char *format) { + ArgFormatter formatter(*this); format_ = format; args_.clear(); return formatter; } -class FullFormat : public BasicArgFormatter { +// A formatter with an action performed when formatting is complete. +template +class ActiveFormatter : public ArgFormatter { private: mutable Formatter formatter_; // Do not implement. - FullFormat& operator=(const FullFormat&); + ActiveFormatter& operator=(const ActiveFormatter&); public: - explicit FullFormat(const char *format) : BasicArgFormatter(formatter_) { - BasicArgFormatter::operator=(formatter_(format)); + explicit ActiveFormatter(const char *format) : ArgFormatter(formatter_) { + ArgFormatter::operator=(formatter_(format)); } - FullFormat(FullFormat& other) : BasicArgFormatter(other) {} + ActiveFormatter(ActiveFormatter& other) : ArgFormatter(other) {} - ~FullFormat() { - FinishFormatting(); + ~ActiveFormatter() { + Action()(*FinishFormatting()); } }; -inline FullFormat Format(const char *format) { - FullFormat ff(format); - return ff; +struct Ignore { + void operator()(Formatter &) const {} +}; + +inline ActiveFormatter Format(const char *format) { + ActiveFormatter af(format); + return af; } -class Print : public BasicArgFormatter { - private: - Formatter formatter_; - - // Do not implement. - Print(const Print&); - Print& operator=(const Print&); - - public: - explicit Print(const char *format) : BasicArgFormatter(formatter_) { - BasicArgFormatter::operator=(formatter_(format)); - } - - ~Print() { - FinishFormatting(); - std::fwrite(formatter_.data(), 1, formatter_.size(), stdout); +struct Write { + void operator()(Formatter &f) const { + std::fwrite(f.data(), 1, f.size(), stdout); } }; + +inline ActiveFormatter Print(const char *format) { + ActiveFormatter af(format); + return af; +} } namespace fmt = format; diff --git a/format_test.cc b/format_test.cc index d510edbf..34826e2f 100644 --- a/format_test.cc +++ b/format_test.cc @@ -525,18 +525,58 @@ TEST(FormatterTest, FormatStringFromSpeedTest) { TEST(FormatterTest, ArgLifetime) { // The following code is for testing purposes only. It is a definite abuse // of the API and shouldn't be used in real applications. - const fmt::BasicArgFormatter &af = fmt::Format("{0}"); - const_cast(af) << std::string("test"); + const fmt::ArgFormatter &af = fmt::Format("{0}"); + const_cast(af) << std::string("test"); // String object passed as an argument to Print has been destroyed, // but BasicArgFormatter dtor hasn't been called yet. EXPECT_EQ("test", str(af)); } -TEST(FormatterTest, Formatter) { +TEST(FormatterTest, FormatterCtor) { + Formatter format; + EXPECT_EQ(0, format.size()); + EXPECT_STREQ("", format.data()); + EXPECT_STREQ("", format.c_str()); + EXPECT_EQ("", format.str()); + format("part{0}") << 1; + format("part{0}") << 2; + EXPECT_EQ("part1part2", format.str()); +} + +TEST(FormatterTest, FormatterAppend) { + Formatter format; + format("part{0}") << 1; + EXPECT_EQ(strlen("part1"), format.size()); + EXPECT_STREQ("part1", format.c_str()); + EXPECT_STREQ("part1", format.data()); + EXPECT_EQ("part1", format.str()); + format("part{0}") << 2; + EXPECT_EQ(strlen("part1part2"), format.size()); + EXPECT_STREQ("part1part2", format.c_str()); + EXPECT_STREQ("part1part2", format.data()); + EXPECT_EQ("part1part2", format.str()); +} + +TEST(FormatterTest, FormatterExample) { Formatter format; format("Current point:\n"); format("({0:+f}, {1:+f})\n") << -3.14 << 3.14; - EXPECT_STREQ("Current point:\n(-3.140000, +3.140000)\n", format.c_str()); + EXPECT_EQ("Current point:\n(-3.140000, +3.140000)\n", format.str()); } -// TODO: test API +struct PrintError { + void operator()(const fmt::Formatter &f) const { + std::cerr << "Error: " << f.str() << std::endl; + } +}; + +fmt::ActiveFormatter ReportError(const char *format) { + fmt::ActiveFormatter af(format); + return af; +} + +TEST(FormatterTest, ArgFormatter) { + std::string path = "somefile"; + ReportError("File not found: {0}") << path; +} +// TODO: test ArgFormatter