fmt support for sign added

This commit is contained in:
Mateusz Pusz
2019-11-12 19:46:15 +01:00
parent d763c75df2
commit ff656620e9
3 changed files with 71 additions and 31 deletions

View File

@@ -547,7 +547,8 @@ The productions `fill-and-align`, `sign`, `width`, and `precision` are described
`precision` specification in the `units-format-spec` is valid only for `units::quantity` types `precision` specification in the `units-format-spec` is valid only for `units::quantity` types
where the representation type `Rep` is a floating point type. For all other `Rep` types, an where the representation type `Rep` is a floating point type. For all other `Rep` types, an
exception of type `format_error` is thrown if the `units-format-spec` contains a precision exception of type `format_error` is thrown if the `units-format-spec` contains a precision
specification. specification. An `format_error` is also thrown if `sign` is provided with a `conversion-spec`
to print quantity unit but not its value.
Each conversion specifier `conversion-spec` is replaced by appropriate characters as described Each conversion specifier `conversion-spec` is replaced by appropriate characters as described
in the following table: in the following table:
@@ -560,7 +561,6 @@ in the following table:
| `%t` | A horizontal-tab character | | `%t` | A horizontal-tab character |
| `%%` | A `%` character | | `%%` | A `%` character |
If the `units-specs` is omitted, the `quantity` object is formatted as if by streaming it to If the `units-specs` is omitted, the `quantity` object is formatted as if by streaming it to
`std::ostringstream os` and copying `os.str()` through the output iterator of the context with `std::ostringstream os` and copying `os.str()` through the output iterator of the context with
additional padding and adjustments as specified by the format specifiers. additional padding and adjustments as specified by the format specifiers.

View File

@@ -25,13 +25,14 @@
#include <units/bits/customization_points.h> #include <units/bits/customization_points.h>
#include <units/quantity.h> #include <units/quantity.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <string_view>
namespace units { namespace units {
namespace detail { namespace detail {
// units-format-spec: // units-format-spec:
// fill-and-align[opt] width[opt] precision[opt] units-specs[opt] // fill-and-align[opt] sign[opt] width[opt] precision[opt] units-specs[opt]
// units-specs: // units-specs:
// conversion-spec // conversion-spec
// units-specs conversion-spec // units-specs conversion-spec
@@ -94,11 +95,25 @@ namespace units {
} }
template<typename Rep, typename OutputIt> template<typename Rep, typename OutputIt>
inline OutputIt format_units_quantity_value(OutputIt out, const Rep& val, int precision) inline OutputIt format_units_quantity_value(OutputIt out, const Rep& val, fmt::sign_t sign, int precision)
{ {
std::string sign_text;
switch(sign) {
case fmt::sign::none:
break;
case fmt::sign::plus:
sign_text = "+";
break;
case fmt::sign::minus:
sign_text = "-";
break;
case fmt::sign::space:
sign_text = " ";
break;
}
if(precision >= 0) if(precision >= 0)
return format_to(out, "{:.{}f}", val, precision); return format_to(out, "{:" + sign_text + ".{}f}", val, precision);
return format_to(out, treat_as_floating_point<Rep> ? "{:g}" : "{}", val); return format_to(out, treat_as_floating_point<Rep> ? "{:" + sign_text + "g}" : "{:" + sign_text + "}", val);
} }
template<typename Unit, typename OutputIt> template<typename Unit, typename OutputIt>
@@ -111,10 +126,11 @@ namespace units {
struct units_formatter { struct units_formatter {
OutputIt out; OutputIt out;
Rep val; Rep val;
fmt::sign_t sign;
int precision; int precision;
explicit units_formatter(OutputIt o, quantity<Unit, Rep> q, int prec): explicit units_formatter(OutputIt o, quantity<Unit, Rep> q, fmt::sign_t s, int prec):
out(o), val(q.count()), precision(prec) out(o), val(q.count()), sign(s), precision(prec)
{ {
} }
@@ -126,7 +142,7 @@ namespace units {
void on_quantity_value() void on_quantity_value()
{ {
out = format_units_quantity_value(out, val, precision); out = format_units_quantity_value(out, val, sign, precision);
} }
void on_quantity_unit() void on_quantity_unit()
@@ -179,27 +195,30 @@ private:
} }
void on_error(const char* msg) { throw fmt::format_error(msg); } void on_error(const char* msg) { throw fmt::format_error(msg); }
void on_fill(CharT fill) { f.specs.fill[0] = fill; } constexpr void on_fill(CharT fill) { f.specs.fill[0] = fill; }
void on_align(align_t align) { f.specs.align = align; } constexpr void on_plus() { f.specs.sign = fmt::sign::plus; }
void on_width(unsigned width) { f.specs.width = width; } constexpr void on_minus() { f.specs.sign = fmt::sign::minus; }
void on_precision(unsigned precision) { f.precision = precision; } constexpr void on_space() { f.specs.sign = fmt::sign::space; }
void end_precision() {} constexpr void on_align(align_t align) { f.specs.align = align; }
constexpr void on_width(unsigned width) { f.specs.width = width; }
constexpr void on_precision(unsigned precision) { f.precision = precision; }
constexpr void end_precision() {}
template<typename Id> template<typename Id>
void on_dynamic_width(Id arg_id) constexpr void on_dynamic_width(Id arg_id)
{ {
f.width_ref = make_arg_ref(arg_id); f.width_ref = make_arg_ref(arg_id);
} }
template<typename Id> template<typename Id>
void on_dynamic_precision(Id arg_id) constexpr void on_dynamic_precision(Id arg_id)
{ {
f.precision_ref = make_arg_ref(arg_id); f.precision_ref = make_arg_ref(arg_id);
} }
void on_text(const CharT*, const CharT*) {} constexpr void on_text(const CharT*, const CharT*) {}
void on_quantity_value() { f.quantity_value = true; } constexpr void on_quantity_value() { f.quantity_value = true; }
void on_quantity_unit() { f.quantity_unit = true; } constexpr void on_quantity_unit() { f.quantity_unit = true; }
}; };
struct parse_range { struct parse_range {
@@ -221,6 +240,24 @@ private:
if(begin == end) if(begin == end)
return {begin, begin}; return {begin, begin};
// parse sign
switch(static_cast<char>(*begin)) {
case '+':
handler.on_plus();
++begin;
break;
case '-':
handler.on_minus();
++begin;
break;
case ' ':
handler.on_space();
++begin;
break;
}
if(begin == end)
return {begin, begin};
// parse width // parse width
begin = fmt::internal::parse_width(begin, end, handler); begin = fmt::internal::parse_width(begin, end, handler);
if(begin == end) if(begin == end)
@@ -241,6 +278,9 @@ private:
// quantity values should behave like numbers (by default aligned to right) // quantity values should behave like numbers (by default aligned to right)
specs.align = fmt::align_t::right; specs.align = fmt::align_t::right;
if((quantity_unit && !quantity_value) && (specs.sign == fmt::sign::plus || specs.sign == fmt::sign::minus))
handler.on_error("sign not allowed for a quantity unit");
return {begin, end}; return {begin, end};
} }
@@ -268,13 +308,13 @@ public:
// deal with quantity content // deal with quantity content
if(begin == end || *begin == '}') { if(begin == end || *begin == '}') {
// default format should print value followed by the unit separeted with 1 space // default format should print value followed by the unit separeted with 1 space
out = units::detail::format_units_quantity_value(out, q.count(), precision); out = units::detail::format_units_quantity_value(out, q.count(), specs.sign, precision);
*out++ = CharT(' '); *out++ = CharT(' ');
units::detail::format_units_quantity_unit<Unit>(out); units::detail::format_units_quantity_unit<Unit>(out);
} }
else { else {
// user provided format // user provided format
units::detail::units_formatter f(out, q, precision); units::detail::units_formatter f(out, q, specs.sign, precision);
parse_units_format(begin, end, f); parse_units_format(begin, end, f);
} }

View File

@@ -755,25 +755,25 @@ TEST_CASE("sign specification", "[text][fmt]")
SECTION("full format {:%Q %q} on a quantity") SECTION("full format {:%Q %q} on a quantity")
{ {
CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", 1m) == "1 m,+1 m,1 m, 1 m"); CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", 1m) == "1m,+1m,1m, 1m");
CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", -1m) == "-1 m,-1 m,-1 m,-1 m"); CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", -1m) == "-1m,-1m,-1m,-1m");
CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", inf) == "inf m,+inf m,inf m, inf m"); CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", inf) == "infm,+infm,infm, infm");
CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", nan) == "nan m,+nan m,nan m, nan m"); CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", nan) == "nanm,+nanm,nanm, nanm");
} }
SECTION("value only format {:%Q} on a quantity") SECTION("value only format {:%Q} on a quantity")
{ {
CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", 1m) == "1 m,+1 m,1 m, 1 m"); CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", 1m) == "1,+1,1, 1");
CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", -1m) == "-1 m,-1 m,-1 m,-1 m"); CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", -1m) == "-1,-1,-1,-1");
CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", inf) == "inf m,+inf m,inf m, inf m"); CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", inf) == "inf,+inf,inf, inf");
CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", nan) == "nan m,+nan m,nan m, nan m"); CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", nan) == "nan,+nan,nan, nan");
} }
} }
TEST_CASE("sign specification for unit only", "[text][fmt][exception]") TEST_CASE("sign specification for unit only", "[text][fmt][exception]")
{ {
CHECK_THROWS_MATCHES(fmt::format("{:+%q}", 1m), fmt::format_error, Message("sign not allowed for quantity unit")); CHECK_THROWS_MATCHES(fmt::format("{:+%q}", 1m), fmt::format_error, Message("sign not allowed for a quantity unit"));
CHECK_THROWS_MATCHES(fmt::format("{:-%q}", 1m), fmt::format_error, Message("sign not allowed for quantity unit")); CHECK_THROWS_MATCHES(fmt::format("{:-%q}", 1m), fmt::format_error, Message("sign not allowed for a quantity unit"));
} }