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)
int argIndex = 0;
auto piece = arg.splitKey<true>(); // 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<Derived, containerMode, Args...>::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<int>(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 <class FormatCallback>
void format(FormatArg& arg, FormatCallback& cb) const {
arg.validate(FormatArg::Type::INTEGER);
......
......@@ -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<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;
}
......
......@@ -127,6 +127,39 @@ class BaseFormatter {
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_;
protected:
......
......@@ -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
......
......@@ -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):
......
......@@ -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<int> 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<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
// 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