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;
}
} 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) {
{
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);
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;
}
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:
thousandsSeparator(false),
trailingDot(false),
width(kDefaultWidth),
+ widthIndex(kNoIndex),
precision(kDefaultPrecision),
presentation(kDefaultPresentation),
nextKeyMode_(NextKeyMode::NONE) {
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
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"
`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):
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));
}
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 '}'");
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