From: Brett Simmers Date: Sun, 9 Aug 2015 22:41:15 +0000 (-0700) Subject: Support dynamic field width in folly::format() X-Git-Tag: v0.53.0~1 X-Git-Url: http://plrg.eecs.uci.edu/git/?a=commitdiff_plain;h=aa7aebf348c1b60d2ef2e81dd526aacd2f8d8769;p=folly.git Support dynamic field width in folly::format() Summary: I added this to support logging with varying indentation levels, but it could also be useful in other situations. Examples are in the test/documentation. Reviewed By: @tudor Differential Revision: D2322206 --- diff --git a/folly/Format-inl.h b/folly/Format-inl.h index 1239188c..56adcd01 100644 --- a/folly/Format-inl.h +++ b/folly/Format-inl.h @@ -225,6 +225,8 @@ void BaseFormatter::operator()(Output& out) int argIndex = 0; auto piece = arg.splitKey(); // empty key component is okay if (containerMode) { // static + arg.enforce(arg.width != FormatArg::kDynamicWidth, + "dynamic field width not supported in vformat()"); if (piece.empty()) { arg.setNextIntKey(nextArg++); hasDefaultArgIndex = true; @@ -234,9 +236,22 @@ void BaseFormatter::operator()(Output& out) } } else { if (piece.empty()) { + if (arg.width == FormatArg::kDynamicWidth) { + arg.enforce(arg.widthIndex == FormatArg::kNoIndex, + "cannot provide width arg index without value arg index"); + int sizeArg = nextArg++; + arg.width = getSizeArg(sizeArg, arg); + } + argIndex = nextArg++; hasDefaultArgIndex = true; } else { + if (arg.width == FormatArg::kDynamicWidth) { + arg.enforce(arg.widthIndex != FormatArg::kNoIndex, + "cannot provide value arg index without width arg index"); + arg.width = getSizeArg(arg.widthIndex, arg); + } + try { argIndex = to(piece); } catch (const std::out_of_range& e) { @@ -402,6 +417,11 @@ class FormatValue< { public: explicit FormatValue(T val) : val_(val) { } + + T getValue() const { + return val_; + } + template void format(FormatArg& arg, FormatCallback& cb) const { arg.validate(FormatArg::Type::INTEGER); diff --git a/folly/Format.cpp b/folly/Format.cpp index 25a436e5..0f67969c 100644 --- a/folly/Format.cpp +++ b/folly/Format.cpp @@ -209,12 +209,25 @@ void FormatArg::initSlow() { if (++p == end) return; } - if (*p >= '0' && *p <= '9') { - auto b = p; + auto readInt = [&] { + auto const b = p; do { ++p; } while (p != end && *p >= '0' && *p <= '9'); - width = to(StringPiece(b, p)); + return to(StringPiece(b, p)); + }; + + if (*p == '*') { + width = kDynamicWidth; + ++p; + + if (p == end) return; + + if (*p >= '0' && *p <= '9') widthIndex = readInt(); + + if (p == end) return; + } else if (*p >= '0' && *p <= '9') { + width = readInt(); if (p == end) return; } diff --git a/folly/Format.h b/folly/Format.h index c7b729d4..e3134a33 100644 --- a/folly/Format.h +++ b/folly/Format.h @@ -127,6 +127,39 @@ class BaseFormatter { return doFormatFrom<0>(i, arg, cb); } + template + typename std::enable_if::type + getSizeArgFrom(size_t i, const FormatArg& arg) const { + arg.error("argument index out of range, max=", i); + } + + template + typename std::enable_if::value && + !std::is_same::value, int>::type + getValue(const FormatValue& format, const FormatArg&) const { + return static_cast(format.getValue()); + } + + template + typename std::enable_if::value || + std::is_same::value, int>::type + getValue(const FormatValue&, const FormatArg& arg) const { + arg.error("dynamic field width argument must be integral"); + } + + template + typename std::enable_if::type + getSizeArgFrom(size_t i, const FormatArg& arg) const { + if (i == K) { + return getValue(std::get(values_), arg); + } + return getSizeArgFrom(i, arg); + } + + int getSizeArg(size_t i, const FormatArg& arg) const { + return getSizeArgFrom<0>(i, arg); + } + StringPiece str_; protected: diff --git a/folly/FormatArg.h b/folly/FormatArg.h index bcf4c476..c4d94736 100644 --- a/folly/FormatArg.h +++ b/folly/FormatArg.h @@ -48,6 +48,7 @@ struct FormatArg { thousandsSeparator(false), trailingDot(false), width(kDefaultWidth), + widthIndex(kNoIndex), precision(kDefaultPrecision), presentation(kDefaultPresentation), nextKeyMode_(NextKeyMode::NONE) { @@ -135,10 +136,13 @@ struct FormatArg { bool trailingDot; /** - * Field width + * Field width and optional argument index */ static constexpr int kDefaultWidth = -1; + static constexpr int kDynamicWidth = -2; + static constexpr int kNoIndex = -1; int width; + int widthIndex; /** * Precision diff --git a/folly/docs/Format.md b/folly/docs/Format.md index 5e40bf5d..b822c21a 100644 --- a/folly/docs/Format.md +++ b/folly/docs/Format.md @@ -82,6 +82,16 @@ std::cout << vformat("{0} {2} {1}", t); std::cout << format("{:X<10} {}", "hello", "world"); // => "helloXXXXX world" +// Field width may be a runtime value rather than part of the format string +int x = 6; +std::cout << format("{:-^*}", x, "hi"); +// => "--hi--" + +// Explicit arguments work with dynamic field width, as long as indexes are +// given for both the value and the field width. +std::cout << format("{2:+^*0}", +9, "unused", 456); // => "+++456+++" + // Format supports printf-style format specifiers std::cout << format("{0:05d} decimal = {0:04x} hex", 42); // => "00042 decimal = 002a hex" @@ -142,7 +152,10 @@ Format specification: `0X` for hexadecimal; only valid for integers) - '`0`': 0-pad after sign, same as specifying "`0=`" as the `fill` and `align` parameters (only valid for numbers) -- `width`: minimum field width +- `width`: minimum field width. May be '`*`' to indicate that the field width + is given by an argument. Defaults to the next argument (preceding the value + to be formatted) but an explicit argument index may be given following the + '`*`'. Not supported in `vformat()`. - '`,`' (comma): output comma as thousands' separator (only valid for integers, and only for decimal output) - `precision` (not allowed for integers): diff --git a/folly/test/FormatTest.cpp b/folly/test/FormatTest.cpp index 4385401b..8676180f 100644 --- a/folly/test/FormatTest.cpp +++ b/folly/test/FormatTest.cpp @@ -105,6 +105,13 @@ TEST(Format, Simple) { EXPECT_EQ("hello ", sformat("{:<7}", "hello")); EXPECT_EQ(" hello", sformat("{:>7}", "hello")); + EXPECT_EQ(" hi", sformat("{:>*}", 4, "hi")); + EXPECT_EQ(" hi!", sformat("{:*}{}", 3, "", "hi!")); + EXPECT_EQ(" 123", sformat("{:*}", 7, 123)); + EXPECT_EQ("123 ", sformat("{:<*}", 7, 123)); + EXPECT_EQ("----<=>----", sformat("{:-^*}", 11, "<=>")); + EXPECT_EQ("+++456+++", sformat("{2:+^*0}", 9, "unused", 456)); + std::vector v1 {10, 20, 30}; EXPECT_EQ("0020", sformat("{0[1]:04}", v1)); EXPECT_EQ("0020", svformat("{1:04}", v1)); @@ -420,7 +427,6 @@ TEST(Format, OutOfBounds) { } TEST(Format, BogusFormatString) { - // format() will crash the program if the format string is invalid. EXPECT_FORMAT_ERROR(sformat("}"), "single '}' in format string"); EXPECT_FORMAT_ERROR(sformat("foo}bar"), "single '}' in format string"); EXPECT_FORMAT_ERROR(sformat("foo{bar"), "missing ending '}'"); @@ -429,6 +435,22 @@ TEST(Format, BogusFormatString) { EXPECT_FORMAT_ERROR(sformat("{1.3}", 0, 1, 2), "index not allowed"); EXPECT_FORMAT_ERROR(sformat("{0} {} {1}", 0, 1, 2), "may not have both default and explicit arg indexes"); + EXPECT_FORMAT_ERROR(sformat("{:*}", 1.2), + "dynamic field width argument must be integral"); + EXPECT_FORMAT_ERROR(sformat("{} {:*}", "hi"), + "argument index out of range, max=1"); + EXPECT_FORMAT_ERROR( + sformat("{:*0}", 12, "ok"), + "cannot provide width arg index without value arg index" + ); + EXPECT_FORMAT_ERROR( + sformat("{0:*}", 12, "ok"), + "cannot provide value arg index without width arg index" + ); + + std::vector v{1, 2, 3}; + EXPECT_FORMAT_ERROR(svformat("{:*}", v), + "dynamic field width not supported in vformat()"); // This one fails in detail::enforceWhitespace(), which throws // std::range_error