Commit aa7aebf3 authored by Brett Simmers's avatar Brett Simmers Committed by facebook-github-bot-1

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
parent a64e4f2d
...@@ -225,6 +225,8 @@ void BaseFormatter<Derived, containerMode, Args...>::operator()(Output& out) ...@@ -225,6 +225,8 @@ void BaseFormatter<Derived, containerMode, Args...>::operator()(Output& out)
int argIndex = 0; int argIndex = 0;
auto piece = arg.splitKey<true>(); // empty key component is okay auto piece = arg.splitKey<true>(); // empty key component is okay
if (containerMode) { // static if (containerMode) { // static
arg.enforce(arg.width != FormatArg::kDynamicWidth,
"dynamic field width not supported in vformat()");
if (piece.empty()) { if (piece.empty()) {
arg.setNextIntKey(nextArg++); arg.setNextIntKey(nextArg++);
hasDefaultArgIndex = true; hasDefaultArgIndex = true;
...@@ -234,9 +236,22 @@ void BaseFormatter<Derived, containerMode, Args...>::operator()(Output& out) ...@@ -234,9 +236,22 @@ void BaseFormatter<Derived, containerMode, Args...>::operator()(Output& out)
} }
} else { } else {
if (piece.empty()) { 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++; argIndex = nextArg++;
hasDefaultArgIndex = true; hasDefaultArgIndex = true;
} else { } 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 { try {
argIndex = to<int>(piece); argIndex = to<int>(piece);
} catch (const std::out_of_range& e) { } catch (const std::out_of_range& e) {
...@@ -402,6 +417,11 @@ class FormatValue< ...@@ -402,6 +417,11 @@ class FormatValue<
{ {
public: public:
explicit FormatValue(T val) : val_(val) { } explicit FormatValue(T val) : val_(val) { }
T getValue() const {
return val_;
}
template <class FormatCallback> template <class FormatCallback>
void format(FormatArg& arg, FormatCallback& cb) const { void format(FormatArg& arg, FormatCallback& cb) const {
arg.validate(FormatArg::Type::INTEGER); arg.validate(FormatArg::Type::INTEGER);
......
...@@ -209,12 +209,25 @@ void FormatArg::initSlow() { ...@@ -209,12 +209,25 @@ void FormatArg::initSlow() {
if (++p == end) return; if (++p == end) return;
} }
if (*p >= '0' && *p <= '9') { auto readInt = [&] {
auto b = p; auto const b = p;
do { do {
++p; ++p;
} while (p != end && *p >= '0' && *p <= '9'); } while (p != end && *p >= '0' && *p <= '9');
width = to<int>(StringPiece(b, p)); return to<int>(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; if (p == end) return;
} }
......
...@@ -127,6 +127,39 @@ class BaseFormatter { ...@@ -127,6 +127,39 @@ class BaseFormatter {
return doFormatFrom<0>(i, arg, cb); return doFormatFrom<0>(i, arg, cb);
} }
template <size_t K>
typename std::enable_if<K == valueCount, int>::type
getSizeArgFrom(size_t i, const FormatArg& arg) const {
arg.error("argument index out of range, max=", i);
}
template <class T>
typename std::enable_if<std::is_integral<T>::value &&
!std::is_same<T, bool>::value, int>::type
getValue(const FormatValue<T>& format, const FormatArg&) const {
return static_cast<int>(format.getValue());
}
template <class T>
typename std::enable_if<!std::is_integral<T>::value ||
std::is_same<T, bool>::value, int>::type
getValue(const FormatValue<T>&, const FormatArg& arg) const {
arg.error("dynamic field width argument must be integral");
}
template <size_t K>
typename std::enable_if<K < valueCount, int>::type
getSizeArgFrom(size_t i, const FormatArg& arg) const {
if (i == K) {
return getValue(std::get<K>(values_), arg);
}
return getSizeArgFrom<K+1>(i, arg);
}
int getSizeArg(size_t i, const FormatArg& arg) const {
return getSizeArgFrom<0>(i, arg);
}
StringPiece str_; StringPiece str_;
protected: protected:
......
...@@ -48,6 +48,7 @@ struct FormatArg { ...@@ -48,6 +48,7 @@ struct FormatArg {
thousandsSeparator(false), thousandsSeparator(false),
trailingDot(false), trailingDot(false),
width(kDefaultWidth), width(kDefaultWidth),
widthIndex(kNoIndex),
precision(kDefaultPrecision), precision(kDefaultPrecision),
presentation(kDefaultPresentation), presentation(kDefaultPresentation),
nextKeyMode_(NextKeyMode::NONE) { nextKeyMode_(NextKeyMode::NONE) {
...@@ -135,10 +136,13 @@ struct FormatArg { ...@@ -135,10 +136,13 @@ struct FormatArg {
bool trailingDot; bool trailingDot;
/** /**
* Field width * Field width and optional argument index
*/ */
static constexpr int kDefaultWidth = -1; static constexpr int kDefaultWidth = -1;
static constexpr int kDynamicWidth = -2;
static constexpr int kNoIndex = -1;
int width; int width;
int widthIndex;
/** /**
* Precision * Precision
......
...@@ -82,6 +82,16 @@ std::cout << vformat("{0} {2} {1}", t); ...@@ -82,6 +82,16 @@ std::cout << vformat("{0} {2} {1}", t);
std::cout << format("{:X<10} {}", "hello", "world"); std::cout << format("{:X<10} {}", "hello", "world");
// => "helloXXXXX 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 // Format supports printf-style format specifiers
std::cout << format("{0:05d} decimal = {0:04x} hex", 42); std::cout << format("{0:05d} decimal = {0:04x} hex", 42);
// => "00042 decimal = 002a hex" // => "00042 decimal = 002a hex"
...@@ -142,7 +152,10 @@ Format specification: ...@@ -142,7 +152,10 @@ Format specification:
`0X` for hexadecimal; only valid for integers) `0X` for hexadecimal; only valid for integers)
- '`0`': 0-pad after sign, same as specifying "`0=`" as the `fill` and - '`0`': 0-pad after sign, same as specifying "`0=`" as the `fill` and
`align` parameters (only valid for numbers) `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, - '`,`' (comma): output comma as thousands' separator (only valid for integers,
and only for decimal output) and only for decimal output)
- `precision` (not allowed for integers): - `precision` (not allowed for integers):
......
...@@ -105,6 +105,13 @@ TEST(Format, Simple) { ...@@ -105,6 +105,13 @@ TEST(Format, Simple) {
EXPECT_EQ("hello ", sformat("{:<7}", "hello")); EXPECT_EQ("hello ", sformat("{:<7}", "hello"));
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<int> v1 {10, 20, 30}; std::vector<int> v1 {10, 20, 30};
EXPECT_EQ("0020", sformat("{0[1]:04}", v1)); EXPECT_EQ("0020", sformat("{0[1]:04}", v1));
EXPECT_EQ("0020", svformat("{1:04}", v1)); EXPECT_EQ("0020", svformat("{1:04}", v1));
...@@ -420,7 +427,6 @@ TEST(Format, OutOfBounds) { ...@@ -420,7 +427,6 @@ TEST(Format, OutOfBounds) {
} }
TEST(Format, BogusFormatString) { 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("}"), "single '}' in format string");
EXPECT_FORMAT_ERROR(sformat("foo}bar"), "single '}' in format string"); EXPECT_FORMAT_ERROR(sformat("foo}bar"), "single '}' in format string");
EXPECT_FORMAT_ERROR(sformat("foo{bar"), "missing ending '}'"); EXPECT_FORMAT_ERROR(sformat("foo{bar"), "missing ending '}'");
...@@ -429,6 +435,22 @@ TEST(Format, BogusFormatString) { ...@@ -429,6 +435,22 @@ TEST(Format, BogusFormatString) {
EXPECT_FORMAT_ERROR(sformat("{1.3}", 0, 1, 2), "index not allowed"); EXPECT_FORMAT_ERROR(sformat("{1.3}", 0, 1, 2), "index not allowed");
EXPECT_FORMAT_ERROR(sformat("{0} {} {1}", 0, 1, 2), EXPECT_FORMAT_ERROR(sformat("{0} {} {1}", 0, 1, 2),
"may not have both default and explicit arg indexes"); "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<int> v{1, 2, 3};
EXPECT_FORMAT_ERROR(svformat("{:*}", v),
"dynamic field width not supported in vformat()");
// This one fails in detail::enforceWhitespace(), which throws // This one fails in detail::enforceWhitespace(), which throws
// std::range_error // std::range_error
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment