Function sample_formatting
Synopsis
#include <samples/quickstart.cpp>
void sample_formatting()
Description
control formatting when serializing/deserializing
ryml provides facilities for formatting (imported from c4core into the ryml namespace)
- See
- https://c4core.docsforge.com/master/formatting-arguments/
- See
- https://c4core.docsforge.com/master/formatting-strings/
Mentioned in
- Getting Started / Quick start
Source
Lines 1943-2371 in samples/quickstart.cpp. Line 60 in samples/quickstart.cpp.
void sample_formatting()
{
// format(), format_sub(), formatrs(): format arguments
{
char buf_[256] = {};
ryml::substr buf = buf_;
size_t size = ryml::format(buf, "a={} foo {} {} bar {}", 0.1, 10, 11, 12);
CHECK(size == strlen("a=0.1 foo 10 11 bar 12"));
CHECK(buf.first(size) == "a=0.1 foo 10 11 bar 12");
// it is safe to call on an empty buffer:
// returns the size needed for the result, and no overflow occurs:
size = ryml::format({} , "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12);
CHECK(size == ryml::format(buf, "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12));
CHECK(size == strlen("a=this_is_a foo 10 11 bar 12"));
// it is also safe to call on an insufficient buffer:
char smallbuf[8] = {};
size = ryml::format(smallbuf, "{} is too large {}", "this", "for the buffer");
CHECK(size == strlen("this is too large for the buffer"));
// ... and the result is truncated at the buffer size:
CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
// format_sub() directly returns the written string:
ryml::csubstr result = ryml::format_sub(buf, "b={}, damn it.", 1);
CHECK(result == "b=1, damn it.");
CHECK(result.is_sub(buf));
// formatrs() means FORMAT & ReSize:
//
// Instead of a substr, it receives any owning linear char container
// for which to_substr() is defined (using ADL).
// <ryml_std.hpp> has to_substr() definitions for std::string and
// std::vector<char>.
//
// formatrs() starts by calling format(), and if needed, resizes the container
// and calls format() again.
//
// Note that unless the container is previously sized, this
// may cause an allocation, which will make your code slower.
// Make sure to call .reserve() on the container for real
// production code.
std::string sbuf;
ryml::formatrs(&sbuf, "and c={} seems about right", 2);
CHECK(sbuf == "and c=2 seems about right");
std::vector<char> vbuf; // works with any linear char container
ryml::formatrs(&vbuf, "and c={} seems about right", 2);
CHECK(sbuf == "and c=2 seems about right");
// with formatrs() it is also possible to append:
ryml::formatrs(ryml::append, &sbuf, ", and finally d={} - done", 3);
CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
}
// unformat(): read arguments - opposite of format()
{
char buf_[256];
int a = 0, b = 1, c = 2;
ryml::csubstr result = ryml::format_sub(buf_, "{} and {} and {}", a, b, c);
CHECK(result == "0 and 1 and 2");
int aa = -1, bb = -2, cc = -3;
size_t num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
CHECK(num_characters == result.size());
CHECK(aa == a);
CHECK(bb == b);
CHECK(cc == c);
result = ryml::format_sub(buf_, "{} and {} and {}", 10, 20, 30);
CHECK(result == "10 and 20 and 30");
num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
CHECK(num_characters == result.size());
CHECK(aa == 10);
CHECK(bb == 20);
CHECK(cc == 30);
}
// cat(), cat_sub(), catrs(): concatenate arguments
{
char buf_[256] = {};
ryml::substr buf = buf_;
size_t size = ryml::cat(buf, "a=", 0.1, "foo", 10, 11, "bar", 12);
CHECK(size == strlen("a=0.1foo1011bar12"));
CHECK(buf.first(size) == "a=0.1foo1011bar12");
// it is safe to call on an empty buffer:
// returns the size needed for the result, and no overflow occurs:
CHECK(ryml::cat({}, "a=", 0) == 3);
// it is also safe to call on an insufficient buffer:
char smallbuf[8] = {};
size = ryml::cat(smallbuf, "this", " is too large ", "for the buffer");
CHECK(size == strlen("this is too large for the buffer"));
// ... and the result is truncated at the buffer size:
CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
// cat_sub() directly returns the written string:
ryml::csubstr result = ryml::cat_sub(buf, "b=", 1, ", damn it.");
CHECK(result == "b=1, damn it.");
CHECK(result.is_sub(buf));
// catrs() means CAT & ReSize:
//
// Instead of a substr, it receives any owning linear char container
// for which to_substr() is defined (using ADL).
// <ryml_std.hpp> has to_substr() definitions for std::string and
// std::vector<char>.
//
// catrs() starts by calling cat(), and if needed, resizes the container
// and calls cat() again.
//
// Note that unless the container is previously sized, this
// may cause an allocation, which will make your code slower.
// Make sure to call .reserve() on the container for real
// production code.
std::string sbuf;
ryml::catrs(&sbuf, "and c=", 2, " seems about right");
CHECK(sbuf == "and c=2 seems about right");
std::vector<char> vbuf; // works with any linear char container
ryml::catrs(&vbuf, "and c=", 2, " seems about right");
CHECK(sbuf == "and c=2 seems about right");
// with catrs() it is also possible to append:
ryml::catrs(ryml::append, &sbuf, ", and finally d=", 3, " - done");
CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
}
// uncat(): read arguments - opposite of cat()
{
char buf_[256];
int a = 0, b = 1, c = 2;
ryml::csubstr result = ryml::cat_sub(buf_, a, ' ', b, ' ', c);
CHECK(result == "0 1 2");
int aa = -1, bb = -2, cc = -3;
char sep1 = 'a', sep2 = 'b';
size_t num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
CHECK(num_characters == result.size());
CHECK(aa == a);
CHECK(bb == b);
CHECK(cc == c);
CHECK(sep1 == ' ');
CHECK(sep2 == ' ');
result = ryml::cat_sub(buf_, 10, ' ', 20, ' ', 30);
CHECK(result == "10 20 30");
num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
CHECK(num_characters == result.size());
CHECK(aa == 10);
CHECK(bb == 20);
CHECK(cc == 30);
CHECK(sep1 == ' ');
CHECK(sep2 == ' ');
}
// catsep(), catsep_sub(), catseprs(): concatenate arguments, with a separator
{
char buf_[256] = {};
ryml::substr buf = buf_;
// use ' ' as a separator
size_t size = ryml::catsep(buf, ' ', "a=", 0, "b=", 1, "c=", 2, 45, 67);
CHECK(buf.first(size) == "a= 0 b= 1 c= 2 45 67");
// any separator may be used
// use " and " as a separator
size = ryml::catsep(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
CHECK(buf.first(size) == "a=0 and b=1 and c=2 and 45 and 67");
// use " ... " as a separator
size = ryml::catsep(buf, " ... ", "a=0", "b=1", "c=2", 45, 67);
CHECK(buf.first(size) == "a=0 ... b=1 ... c=2 ... 45 ... 67");
// use '/' as a separator
size = ryml::catsep(buf, '/', "a=", 0, "b=", 1, "c=", 2, 45, 67);
CHECK(buf.first(size) == "a=/0/b=/1/c=/2/45/67");
// use 888 as a separator
size = ryml::catsep(buf, 888, "a=0", "b=1", "c=2", 45, 67);
CHECK(buf.first(size) == "a=0888b=1888c=28884588867");
// it is safe to call on an empty buffer:
// returns the size needed for the result, and no overflow occurs:
CHECK(size == ryml::catsep({}, 888, "a=0", "b=1", "c=2", 45, 67));
// it is also safe to call on an insufficient buffer:
char smallbuf[8] = {};
CHECK(size == ryml::catsep(smallbuf, 888, "a=0", "b=1", "c=2", 45, 67));
CHECK(size == strlen("a=0888b=1888c=28884588867"));
// ... and the result is truncated:
CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "a=0888b\0");
// catsep_sub() directly returns the written substr:
ryml::csubstr result = ryml::catsep_sub(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
CHECK(result == "a=0 and b=1 and c=2 and 45 and 67");
CHECK(result.is_sub(buf));
// catseprs() means CATSEP & ReSize:
//
// Instead of a substr, it receives any owning linear char container
// for which to_substr() is defined (using ADL).
// <ryml_std.hpp> has to_substr() definitions for std::string and
// std::vector<char>.
//
// catseprs() starts by calling catsep(), and if needed, resizes the container
// and calls catsep() again.
//
// Note that unless the container is previously sized, this
// may cause an allocation, which will make your code slower.
// Make sure to call .reserve() on the container for real
// production code.
std::string sbuf;
ryml::catseprs(&sbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
CHECK(sbuf == "a=0 and b=1 and c=2 and 45 and 67");
std::vector<char> vbuf; // works with any linear char container
ryml::catseprs(&vbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
CHECK(ryml::to_csubstr(vbuf) == "a=0 and b=1 and c=2 and 45 and 67");
// with catseprs() it is also possible to append:
ryml::catseprs(ryml::append, &sbuf, " well ", " --- a=0", "b=11", "c=12", 145, 167);
CHECK(sbuf == "a=0 and b=1 and c=2 and 45 and 67 --- a=0 well b=11 well c=12 well 145 well 167");
}
// uncatsep(): read arguments with a separator - opposite of catsep()
{
char buf_[256] = {};
int a = 0, b = 1, c = 2;
ryml::csubstr result = ryml::catsep_sub(buf_, ' ', a, b, c);
CHECK(result == "0 1 2");
int aa = -1, bb = -2, cc = -3;
char sep = 'b';
size_t num_characters = ryml::uncatsep(result, sep, aa, bb, cc);
CHECK(num_characters == result.size());
CHECK(aa == a);
CHECK(bb == b);
CHECK(cc == c);
CHECK(sep == ' ');
sep = '_';
result = ryml::catsep_sub(buf_, ' ', 10, 20, 30);
CHECK(result == "10 20 30");
num_characters = ryml::uncatsep(result, sep, aa, bb, cc);
CHECK(num_characters == result.size());
CHECK(aa == 10);
CHECK(bb == 20);
CHECK(cc == 30);
CHECK(sep == ' ');
}
// formatting individual arguments
{
using namespace ryml; // all the symbols below are in the ryml namespace.
char buf_[256] = {}; // all the results below are written in this buffer
substr buf = buf_;
// --------------------------------------
// fmt::boolalpha(): format as true/false
// --------------------------------------
// just as with std streams, printing a bool will output the integer value:
CHECK("0" == cat_sub(buf, false));
CHECK("1" == cat_sub(buf, true));
// to force a "true"/"false", use fmt::boolalpha:
CHECK("false" == cat_sub(buf, fmt::boolalpha(false)));
CHECK("true" == cat_sub(buf, fmt::boolalpha(true)));
// ---------------------------------
// fmt::hex(): format as hexadecimal
// ---------------------------------
CHECK("0xff" == cat_sub(buf, fmt::hex(255)));
CHECK("0x100" == cat_sub(buf, fmt::hex(256)));
CHECK("-0xff" == cat_sub(buf, fmt::hex(-255)));
CHECK("-0x100" == cat_sub(buf, fmt::hex(-256)));
CHECK("3735928559" == cat_sub(buf, UINT32_C(0xdeadbeef)));
CHECK("0xdeadbeef" == cat_sub(buf, fmt::hex(UINT32_C(0xdeadbeef))));
// ----------------------------
// fmt::bin(): format as binary
// ----------------------------
CHECK("0b1000" == cat_sub(buf, fmt::bin(8)));
CHECK("0b1001" == cat_sub(buf, fmt::bin(9)));
CHECK("0b10001" == cat_sub(buf, fmt::bin(17)));
CHECK("0b11001" == cat_sub(buf, fmt::bin(25)));
CHECK("-0b1000" == cat_sub(buf, fmt::bin(-8)));
CHECK("-0b1001" == cat_sub(buf, fmt::bin(-9)));
CHECK("-0b10001" == cat_sub(buf, fmt::bin(-17)));
CHECK("-0b11001" == cat_sub(buf, fmt::bin(-25)));
// ---------------------------
// fmt::bin(): format as octal
// ---------------------------
CHECK("0o77" == cat_sub(buf, fmt::oct(63)));
CHECK("0o100" == cat_sub(buf, fmt::oct(64)));
CHECK("0o377" == cat_sub(buf, fmt::oct(255)));
CHECK("0o400" == cat_sub(buf, fmt::oct(256)));
CHECK("0o1000" == cat_sub(buf, fmt::oct(512)));
CHECK("-0o77" == cat_sub(buf, fmt::oct(-63)));
CHECK("-0o100" == cat_sub(buf, fmt::oct(-64)));
CHECK("-0o377" == cat_sub(buf, fmt::oct(-255)));
CHECK("-0o400" == cat_sub(buf, fmt::oct(-256)));
CHECK("-0o1000" == cat_sub(buf, fmt::oct(-512)));
// ---------------------------
// fmt::zpad(): pad with zeros
// ---------------------------
CHECK("000063" == cat_sub(buf, fmt::zpad(63, 6)));
CHECK( "00063" == cat_sub(buf, fmt::zpad(63, 5)));
CHECK( "0063" == cat_sub(buf, fmt::zpad(63, 4)));
CHECK( "063" == cat_sub(buf, fmt::zpad(63, 3)));
CHECK( "63" == cat_sub(buf, fmt::zpad(63, 2)));
CHECK( "63" == cat_sub(buf, fmt::zpad(63, 1))); // will never trim the result
CHECK( "63" == cat_sub(buf, fmt::zpad(63, 0))); // will never trim the result
CHECK("0x00003f" == cat_sub(buf, fmt::zpad(fmt::hex(63), 6)));
CHECK("0o000077" == cat_sub(buf, fmt::zpad(fmt::oct(63), 6)));
CHECK("0b00011001" == cat_sub(buf, fmt::zpad(fmt::bin(25), 8)));
// ------------------------------------------------
// fmt::left(): align left with a given field width
// ------------------------------------------------
CHECK("63 " == cat_sub(buf, fmt::left(63, 6)));
CHECK("63 " == cat_sub(buf, fmt::left(63, 5)));
CHECK("63 " == cat_sub(buf, fmt::left(63, 4)));
CHECK("63 " == cat_sub(buf, fmt::left(63, 3)));
CHECK("63" == cat_sub(buf, fmt::left(63, 2)));
CHECK("63" == cat_sub(buf, fmt::left(63, 1))); // will never trim the result
CHECK("63" == cat_sub(buf, fmt::left(63, 0))); // will never trim the result
// the fill character can be specified (defaults to ' '):
CHECK("63----" == cat_sub(buf, fmt::left(63, 6, '-')));
CHECK("63++++" == cat_sub(buf, fmt::left(63, 6, '+')));
CHECK("63////" == cat_sub(buf, fmt::left(63, 6, '/')));
CHECK("630000" == cat_sub(buf, fmt::left(63, 6, '0')));
CHECK("63@@@@" == cat_sub(buf, fmt::left(63, 6, '@')));
CHECK("0x003f " == cat_sub(buf, fmt::left(fmt::zpad(fmt::hex(63), 4), 10)));
// --------------------------------------------------
// fmt::right(): align right with a given field width
// --------------------------------------------------
CHECK(" 63" == cat_sub(buf, fmt::right(63, 6)));
CHECK(" 63" == cat_sub(buf, fmt::right(63, 5)));
CHECK(" 63" == cat_sub(buf, fmt::right(63, 4)));
CHECK(" 63" == cat_sub(buf, fmt::right(63, 3)));
CHECK("63" == cat_sub(buf, fmt::right(63, 2)));
CHECK("63" == cat_sub(buf, fmt::right(63, 1))); // will never trim the result
CHECK("63" == cat_sub(buf, fmt::right(63, 0))); // will never trim the result
// the fill character can be specified (defaults to ' '):
CHECK("----63" == cat_sub(buf, fmt::right(63, 6, '-')));
CHECK("++++63" == cat_sub(buf, fmt::right(63, 6, '+')));
CHECK("////63" == cat_sub(buf, fmt::right(63, 6, '/')));
CHECK("000063" == cat_sub(buf, fmt::right(63, 6, '0')));
CHECK("@@@@63" == cat_sub(buf, fmt::right(63, 6, '@')));
CHECK(" 0x003f" == cat_sub(buf, fmt::right(fmt::zpad(fmt::hex(63), 4), 10)));
// ------------------------------------------
// fmt::real(): format floating point numbers
// ------------------------------------------
CHECK("0" == cat_sub(buf, fmt::real(0.01f, 0)));
CHECK("0.0" == cat_sub(buf, fmt::real(0.01f, 1)));
CHECK("0.01" == cat_sub(buf, fmt::real(0.01f, 2)));
CHECK("0.010" == cat_sub(buf, fmt::real(0.01f, 3)));
CHECK("0.0100" == cat_sub(buf, fmt::real(0.01f, 4)));
CHECK("0.01000" == cat_sub(buf, fmt::real(0.01f, 5)));
CHECK("1" == cat_sub(buf, fmt::real(1.01f, 0)));
CHECK("1.0" == cat_sub(buf, fmt::real(1.01f, 1)));
CHECK("1.01" == cat_sub(buf, fmt::real(1.01f, 2)));
CHECK("1.010" == cat_sub(buf, fmt::real(1.01f, 3)));
CHECK("1.0100" == cat_sub(buf, fmt::real(1.01f, 4)));
CHECK("1.01000" == cat_sub(buf, fmt::real(1.01f, 5)));
CHECK("1" == cat_sub(buf, fmt::real(1.234234234, 0)));
CHECK("1.2" == cat_sub(buf, fmt::real(1.234234234, 1)));
CHECK("1.23" == cat_sub(buf, fmt::real(1.234234234, 2)));
CHECK("1.234" == cat_sub(buf, fmt::real(1.234234234, 3)));
CHECK("1.2342" == cat_sub(buf, fmt::real(1.234234234, 4)));
CHECK("1.23423" == cat_sub(buf, fmt::real(1.234234234, 5)));
CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5)));
CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5)));
// AKA %f
CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLOAT))); // AKA %f, same as above
CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLOAT))); // AKA %f
CHECK("1234234.2342" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLOAT))); // AKA %f
CHECK("1234234.234" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLOAT))); // AKA %f
CHECK("1234234.23" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLOAT))); // AKA %f
// AKA %e
CHECK("1.00000e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_SCIENT))); // AKA %e
CHECK("1.23423e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_SCIENT))); // AKA %e
CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_SCIENT))); // AKA %e
CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_SCIENT))); // AKA %e
CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_SCIENT))); // AKA %e
// AKA %g
CHECK("1e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLEX))); // AKA %g
CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLEX))); // AKA %g
CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLEX))); // AKA %g
CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLEX))); // AKA %g
CHECK("1.2e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLEX))); // AKA %g
// AKA %a (hexadecimal formatting of floats)
CHECK("0x1.e8480p+19" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_HEXA))); // AKA %a
CHECK("0x1.2d53ap+20" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_HEXA))); // AKA %a
CHECK("0x1.2d54p+20" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_HEXA))); // AKA %a
CHECK("0x1.2d5p+20" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_HEXA))); // AKA %a
CHECK("0x1.2dp+20" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_HEXA))); // AKA %a
// --------------------------------------------------------------
// fmt::raw(): dump data in machine format (respecting alignment)
// --------------------------------------------------------------
{
C4_SUPPRESS_WARNING_CLANG_WITH_PUSH("-Wcast-align") // we're casting the values directly, so alignment is strictly respected.
const uint32_t payload[] = {10, 20, 30, 40, UINT32_C(0xdeadbeef)};
// (package payload as a substr, for comparison only)
csubstr expected = csubstr((const char *)payload, sizeof(payload));
csubstr actual = cat_sub(buf, fmt::raw(payload));
CHECK(!actual.overlaps(expected));
CHECK(0 == memcmp(expected.str, actual.str, expected.len));
// also possible with variables:
for(const uint32_t value : payload)
{
// (package payload as a substr, for comparison only)
expected = csubstr((const char *)&value, sizeof(value));
actual = cat_sub(buf, fmt::raw(value));
CHECK(actual.size() == sizeof(uint32_t));
CHECK(!actual.overlaps(expected));
CHECK(0 == memcmp(expected.str, actual.str, expected.len));
// with non-const data, fmt::craw() may be needed for disambiguation:
actual = cat_sub(buf, fmt::craw(value));
CHECK(actual.size() == sizeof(uint32_t));
CHECK(!actual.overlaps(expected));
CHECK(0 == memcmp(expected.str, actual.str, expected.len));
//
// read back:
uint32_t result;
auto reader = fmt::raw(result); // keeps a reference to result
CHECK(&result == (uint32_t*)reader.buf);
CHECK(reader.len == sizeof(uint32_t));
uncat(actual, reader);
// and compare:
// (vs2017/release/32bit does not reload result from cache, so force it)
result = *(uint32_t*)reader.buf;
CHECK(result == value); // roundtrip completed successfully
}
C4_SUPPRESS_WARNING_CLANG_POP
}
// -------------------------
// fmt::base64(): see below!
// -------------------------
}
}