2018-06-02 06:21:27 +03:00
|
|
|
/*
|
|
|
|
|
__ _____ _____ _____
|
|
|
|
|
__| | __| | | | JSON for Modern C++ (test suite)
|
|
|
|
|
| | |__ | | | | | | version 3.1.2
|
|
|
|
|
|_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
|
|
|
|
|
|
|
|
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
|
|
|
SPDX-License-Identifier: MIT
|
2018-06-02 07:26:33 +03:00
|
|
|
Copyright (c) 2018 Evan Driscoll <evaned@gmail.com>
|
2018-06-02 06:21:27 +03:00
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
|
|
|
copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
|
SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "catch.hpp"
|
2018-06-04 06:41:42 +03:00
|
|
|
#include <iostream>
|
2018-06-02 06:21:27 +03:00
|
|
|
|
|
|
|
|
#include <nlohmann/json.hpp>
|
2018-06-02 07:01:15 +03:00
|
|
|
|
2018-06-02 06:21:27 +03:00
|
|
|
using nlohmann::json;
|
2018-06-04 06:41:42 +03:00
|
|
|
using nlohmann::json_pointer;
|
2018-06-02 07:01:15 +03:00
|
|
|
using nlohmann::fancy_dump;
|
2018-06-05 06:41:11 +03:00
|
|
|
using nlohmann::print_style;
|
|
|
|
|
using nlohmann::print_stylizer;
|
2018-06-02 06:21:27 +03:00
|
|
|
|
2018-06-03 06:10:16 +03:00
|
|
|
// Chops off the first line (if empty, but if it *isn't* empty you're
|
|
|
|
|
// probably using this wrong), measures the leading indent on the
|
|
|
|
|
// *next* line, then chops that amount off of all subsequent lines.
|
|
|
|
|
std::string dedent(const char* str)
|
|
|
|
|
{
|
|
|
|
|
std::stringstream out;
|
|
|
|
|
std::stringstream ss(str);
|
|
|
|
|
std::string line;
|
|
|
|
|
bool first = true;
|
|
|
|
|
int indent = -1;
|
|
|
|
|
|
|
|
|
|
while (getline(ss, line))
|
|
|
|
|
{
|
|
|
|
|
if (first && line.empty())
|
|
|
|
|
{
|
|
|
|
|
first = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (indent == -1)
|
|
|
|
|
{
|
|
|
|
|
indent = line.find_first_not_of(' ');
|
|
|
|
|
assert(indent != std::string::npos);
|
|
|
|
|
}
|
|
|
|
|
out << line.c_str() + indent << "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string ans = out.str();
|
|
|
|
|
if (ans[ans.size() - 1] == '\n' and str[strlen(str) - 1] != '\n')
|
|
|
|
|
{
|
|
|
|
|
ans.resize(ans.size() - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ans;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-05 06:41:11 +03:00
|
|
|
std::string fancy_to_string(json j, print_style style = print_style())
|
2018-06-02 09:07:25 +03:00
|
|
|
{
|
|
|
|
|
std::stringstream ss;
|
2018-06-02 09:18:55 +03:00
|
|
|
fancy_dump(ss, j, style);
|
2018-06-02 09:07:25 +03:00
|
|
|
return ss.str();
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-05 06:41:11 +03:00
|
|
|
std::string fancy_to_string(json j, print_stylizer stylizer)
|
2018-06-03 07:10:28 +03:00
|
|
|
{
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
fancy_dump(ss, j, stylizer);
|
|
|
|
|
return ss.str();
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-02 06:21:27 +03:00
|
|
|
TEST_CASE("serialization")
|
|
|
|
|
{
|
2018-06-02 07:26:33 +03:00
|
|
|
SECTION("primitives")
|
2018-06-02 06:21:27 +03:00
|
|
|
{
|
2018-06-02 07:26:33 +03:00
|
|
|
SECTION("null")
|
2018-06-02 06:21:27 +03:00
|
|
|
{
|
2018-06-02 09:07:25 +03:00
|
|
|
auto str = fancy_to_string({});
|
|
|
|
|
CHECK(str == "null");
|
2018-06-02 07:26:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("true")
|
|
|
|
|
{
|
2018-06-02 09:07:25 +03:00
|
|
|
auto str = fancy_to_string(true);
|
|
|
|
|
CHECK(str == "true");
|
2018-06-02 07:26:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("false")
|
|
|
|
|
{
|
2018-06-02 09:07:25 +03:00
|
|
|
auto str = fancy_to_string(false);
|
|
|
|
|
CHECK(str == "false");
|
2018-06-02 07:26:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("integer")
|
|
|
|
|
{
|
2018-06-02 09:07:25 +03:00
|
|
|
auto str = fancy_to_string(10);
|
|
|
|
|
CHECK(str == "10");
|
2018-06-02 07:26:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("floating point")
|
|
|
|
|
{
|
2018-06-02 09:07:25 +03:00
|
|
|
auto str = fancy_to_string(7.5);
|
|
|
|
|
CHECK(str == "7.5");
|
2018-06-02 06:21:27 +03:00
|
|
|
}
|
|
|
|
|
}
|
2018-06-02 09:07:25 +03:00
|
|
|
|
2018-06-02 10:09:04 +03:00
|
|
|
SECTION("strings")
|
|
|
|
|
{
|
|
|
|
|
SECTION("long strings usually print")
|
|
|
|
|
{
|
|
|
|
|
auto str = fancy_to_string(
|
|
|
|
|
"The quick brown fox jumps over the lazy brown dog");
|
|
|
|
|
CHECK(str ==
|
|
|
|
|
"\"The quick brown fox jumps over the lazy brown dog\"");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("long strings can be shortened")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style;
|
2018-06-02 10:09:04 +03:00
|
|
|
style.strings_maximum_length = 10;
|
|
|
|
|
|
|
|
|
|
auto str = fancy_to_string(
|
|
|
|
|
"The quick brown fox jumps over the lazy brown dog",
|
|
|
|
|
style);
|
|
|
|
|
CHECK(str == "\"The qu...g\"");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("requesting extremely short strings limits what is included")
|
|
|
|
|
{
|
|
|
|
|
const char* const quick = "The quick brown fox jumps over the lazy brown dog";
|
|
|
|
|
|
|
|
|
|
std::pair<unsigned, const char*> tests[] =
|
|
|
|
|
{
|
|
|
|
|
{5, "\"T...g\""},
|
|
|
|
|
{4, "\"T...\""},
|
|
|
|
|
{3, "\"...\""},
|
|
|
|
|
{2, "\"..\""},
|
|
|
|
|
{1, "\".\""},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (auto test : tests)
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style;
|
2018-06-02 10:09:04 +03:00
|
|
|
style.strings_maximum_length = test.first;
|
|
|
|
|
auto str = fancy_to_string(quick, style);
|
|
|
|
|
CHECK(str == test.second);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("But you cannot ask for a length of zero; that means unlimited")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style;
|
2018-06-02 10:09:04 +03:00
|
|
|
style.strings_maximum_length = 0;
|
|
|
|
|
|
|
|
|
|
auto str = fancy_to_string(
|
|
|
|
|
"The quick brown fox jumps over the lazy brown dog",
|
|
|
|
|
style);
|
|
|
|
|
CHECK(str ==
|
|
|
|
|
"\"The quick brown fox jumps over the lazy brown dog\"");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("\"Limiting\" to something long doesn't do anything")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style;
|
2018-06-02 10:09:04 +03:00
|
|
|
style.strings_maximum_length = 100;
|
|
|
|
|
|
|
|
|
|
auto str = fancy_to_string(
|
|
|
|
|
"The quick brown fox jumps over the lazy brown dog",
|
|
|
|
|
style);
|
|
|
|
|
CHECK(str ==
|
|
|
|
|
"\"The quick brown fox jumps over the lazy brown dog\"");
|
|
|
|
|
}
|
2018-06-03 04:18:55 +03:00
|
|
|
|
|
|
|
|
// TODO: Handle escape sequences. Figure out what we want the
|
|
|
|
|
// behavior to be, first. :-)
|
2018-06-02 10:09:04 +03:00
|
|
|
}
|
|
|
|
|
|
2018-06-03 06:22:24 +03:00
|
|
|
SECTION("maximum depth")
|
|
|
|
|
{
|
|
|
|
|
SECTION("recursing past the maximum depth with a list elides the subobjects")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style;
|
2018-06-03 06:22:24 +03:00
|
|
|
style.depth_limit = 1;
|
|
|
|
|
|
|
|
|
|
auto str_flat = fancy_to_string({1, {1}}, style);
|
|
|
|
|
CHECK(str_flat == "[1,[...]]");
|
|
|
|
|
|
2018-06-05 06:41:11 +03:00
|
|
|
style = print_style::preset_multiline;
|
2018-06-05 05:59:21 +03:00
|
|
|
style.depth_limit = 1;
|
2018-06-03 06:22:24 +03:00
|
|
|
auto str_lines = fancy_to_string({1, {1}}, style);
|
|
|
|
|
CHECK(str_lines == dedent(R"(
|
|
|
|
|
[
|
|
|
|
|
1,
|
|
|
|
|
[...]
|
|
|
|
|
])"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("recursing past the maximum depth with an object elides the subobjects")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style;
|
2018-06-03 06:22:24 +03:00
|
|
|
style.depth_limit = 1;
|
|
|
|
|
|
|
|
|
|
auto str_flat = fancy_to_string({1, {{"one", 1}}}, style);
|
|
|
|
|
CHECK(str_flat == "[1,{...}]");
|
|
|
|
|
|
2018-06-05 06:41:11 +03:00
|
|
|
style = print_style::preset_multiline;
|
2018-06-05 05:59:21 +03:00
|
|
|
style.depth_limit = 1;
|
2018-06-03 06:22:24 +03:00
|
|
|
auto str_lines = fancy_to_string({1, {{"one", 1}}}, style);
|
|
|
|
|
CHECK(str_lines == dedent(R"(
|
|
|
|
|
[
|
|
|
|
|
1,
|
|
|
|
|
{...}
|
|
|
|
|
])"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-03 07:10:28 +03:00
|
|
|
SECTION("changing styles")
|
|
|
|
|
{
|
|
|
|
|
SECTION("can style objects of a key differently")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_stylizer stylizer;
|
|
|
|
|
stylizer.get_default_style() = print_style::preset_multiline;
|
2018-06-05 05:59:21 +03:00
|
|
|
stylizer.register_key_matcher_style("one line");
|
2018-06-03 07:10:28 +03:00
|
|
|
|
|
|
|
|
auto str = fancy_to_string(
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
"one line", {1, 2}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"two lines", {1, 2}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
stylizer);
|
|
|
|
|
|
|
|
|
|
CHECK(str == dedent(R"(
|
|
|
|
|
{
|
|
|
|
|
"one line": [1,2],
|
|
|
|
|
"two lines": [
|
|
|
|
|
1,
|
|
|
|
|
2
|
|
|
|
|
]
|
|
|
|
|
})"));
|
|
|
|
|
}
|
2018-06-03 07:17:42 +03:00
|
|
|
|
|
|
|
|
SECTION("changes propagate (unless overridden)")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_stylizer stylizer;
|
|
|
|
|
stylizer.get_default_style() = print_style::preset_multiline;
|
2018-06-05 05:59:21 +03:00
|
|
|
stylizer.register_key_matcher_style("one line");
|
2018-06-03 07:17:42 +03:00
|
|
|
|
|
|
|
|
auto str = fancy_to_string(
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
"one line", {{"still one line", {1, 2}}}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
stylizer);
|
|
|
|
|
|
|
|
|
|
CHECK(str == dedent(R"(
|
|
|
|
|
{
|
|
|
|
|
"one line": {"still one line":[1,2]}
|
|
|
|
|
})"));
|
|
|
|
|
}
|
2018-06-04 06:41:42 +03:00
|
|
|
|
2018-06-05 05:59:21 +03:00
|
|
|
SECTION("example of more sophisticated context matcher")
|
2018-06-04 06:41:42 +03:00
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_stylizer stylizer;
|
|
|
|
|
stylizer.get_default_style() = print_style::preset_multiline;
|
2018-06-04 06:41:42 +03:00
|
|
|
|
Refactor: rename the register_style overloads
I really really wanted to name these the same and overload them,
but I couldn't get the metaprogramming to work right. Here's a
comment I wrote that describes the problems and what I *planned*
to do:
// What we want is the register_style overloads below. I chose to
// keep them with the same name. But there are two problems with
// that. First, because I need to wrap them in a std::function
// when promoting to two arguments, I want to make register_style
// themselves take the function parameter by a template argument
// so it doesn't get type-erased "twice" (with two virtual
// calls). But then that means that both versions would have the
// generic signature "template <typename Predicate>
// ... (Predicate, style)" and that would lead to ambiguous calls.
//
// The second problem is that ever if you keep the first parameter
// a std::function, because 'json_pointer' is implicitly
// convertible to a 'json', if you rely on the implicit conversion
// to std::function then you'd get an ambugious call.
//
// So what we want is, using Concept terms:
//
// template <JsonCallable Predicate> ... (Predicate, style)
// template <JsonPointerCallable Predicate> ... (Predicate, style)
//
// where JsonCallable is additionally *not*
// JsonPointerCallable. The following is my attempt to get that.
I then wrote some code that is similar to this:
#include <functional>
struct Main {};
struct Secondary { Secondary(Main); };
// http://en.cppreference.com/w/cpp/types/void_t
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
template <typename, typename = void>
struct can_be_called_with_main : std::false_type { };
template <typename T>
struct can_be_called_with_main<
T,
void_t<decltype(std::declval<T>()(std::declval<Main>()))>
>: std::true_type { };
template <typename, typename = void>
struct can_be_called_with_secondary : std::false_type { };
template <typename T>
struct can_be_called_with_secondary<
T,
void_t<decltype(std::declval<T>()(std::declval<Secondary>()))>
>: std::true_type { };
template <typename Functor>
auto
func(Functor f)
-> typename std::enable_if<can_be_called_with_main<Functor>::value, int>::type
{
return 0;
}
template <typename Functor>
auto
func(Functor f)
-> typename std::enable_if<
can_be_called_with_secondary<Functor>::value
&& !can_be_called_with_main<Functor>::value
, int>::type
{
return 0;
}
auto x1 = func([] (Main) {});
auto x2 = func([] (Secondary) {});
where Main is like 'json' and Secondary like 'json_pointer'.
Problem is it doesn't work -- in the SFIANE context, it looks like
predicates of both `bool (json)` and `bool (json_pointer)` are callable
with both json and json_pointer objects.
In the case of `bool (json)` being called with a 'json_pointer', that
is via the implicit conversion discussed in the comment above. In the
caes of `bool (json_pointer)` being called with a `json`, my guess
as to what is going on is that `json` provides an implicit to-anything
conversion, which uses a `from_json` function. However, that isn't
implemented in a SFIANE-friendly way -- when you try to actually make
that conversion, there's a static_assert failure.
An alternative approach would be to extract the first argument to
the provided predicate via some technique like those described in
https://functionalcpp.wordpress.com/2013/08/05/function-traits/,
and then is_same them vs json and json_pointer.
2018-06-05 06:28:58 +03:00
|
|
|
stylizer.register_style_context_pred(
|
|
|
|
|
[] (const json_pointer<json>& context)
|
2018-06-04 06:41:42 +03:00
|
|
|
{
|
|
|
|
|
// Matches if context[-2] is "each elem on one line"
|
|
|
|
|
return (context.cend() - context.cbegin() >= 2)
|
|
|
|
|
&& (*(context.cend() - 2) == "each elem on one line");
|
Refactor: rename the register_style overloads
I really really wanted to name these the same and overload them,
but I couldn't get the metaprogramming to work right. Here's a
comment I wrote that describes the problems and what I *planned*
to do:
// What we want is the register_style overloads below. I chose to
// keep them with the same name. But there are two problems with
// that. First, because I need to wrap them in a std::function
// when promoting to two arguments, I want to make register_style
// themselves take the function parameter by a template argument
// so it doesn't get type-erased "twice" (with two virtual
// calls). But then that means that both versions would have the
// generic signature "template <typename Predicate>
// ... (Predicate, style)" and that would lead to ambiguous calls.
//
// The second problem is that ever if you keep the first parameter
// a std::function, because 'json_pointer' is implicitly
// convertible to a 'json', if you rely on the implicit conversion
// to std::function then you'd get an ambugious call.
//
// So what we want is, using Concept terms:
//
// template <JsonCallable Predicate> ... (Predicate, style)
// template <JsonPointerCallable Predicate> ... (Predicate, style)
//
// where JsonCallable is additionally *not*
// JsonPointerCallable. The following is my attempt to get that.
I then wrote some code that is similar to this:
#include <functional>
struct Main {};
struct Secondary { Secondary(Main); };
// http://en.cppreference.com/w/cpp/types/void_t
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
template <typename, typename = void>
struct can_be_called_with_main : std::false_type { };
template <typename T>
struct can_be_called_with_main<
T,
void_t<decltype(std::declval<T>()(std::declval<Main>()))>
>: std::true_type { };
template <typename, typename = void>
struct can_be_called_with_secondary : std::false_type { };
template <typename T>
struct can_be_called_with_secondary<
T,
void_t<decltype(std::declval<T>()(std::declval<Secondary>()))>
>: std::true_type { };
template <typename Functor>
auto
func(Functor f)
-> typename std::enable_if<can_be_called_with_main<Functor>::value, int>::type
{
return 0;
}
template <typename Functor>
auto
func(Functor f)
-> typename std::enable_if<
can_be_called_with_secondary<Functor>::value
&& !can_be_called_with_main<Functor>::value
, int>::type
{
return 0;
}
auto x1 = func([] (Main) {});
auto x2 = func([] (Secondary) {});
where Main is like 'json' and Secondary like 'json_pointer'.
Problem is it doesn't work -- in the SFIANE context, it looks like
predicates of both `bool (json)` and `bool (json_pointer)` are callable
with both json and json_pointer objects.
In the case of `bool (json)` being called with a 'json_pointer', that
is via the implicit conversion discussed in the comment above. In the
caes of `bool (json_pointer)` being called with a `json`, my guess
as to what is going on is that `json` provides an implicit to-anything
conversion, which uses a `from_json` function. However, that isn't
implemented in a SFIANE-friendly way -- when you try to actually make
that conversion, there's a static_assert failure.
An alternative approach would be to extract the first argument to
the provided predicate via some technique like those described in
https://functionalcpp.wordpress.com/2013/08/05/function-traits/,
and then is_same them vs json and json_pointer.
2018-06-05 06:28:58 +03:00
|
|
|
}
|
2018-06-04 06:41:42 +03:00
|
|
|
).space_after_comma = true;
|
|
|
|
|
|
|
|
|
|
auto str = fancy_to_string(
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
"each elem on one line", {
|
|
|
|
|
{1, 2, 3, 4, 5},
|
|
|
|
|
{1, 2, 3, 4, 5}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"fully multiline", {
|
|
|
|
|
{1, 2, 3},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
stylizer);
|
|
|
|
|
|
|
|
|
|
CHECK(str == dedent(R"(
|
|
|
|
|
{
|
|
|
|
|
"each elem on one line": [
|
|
|
|
|
[1, 2, 3, 4, 5],
|
|
|
|
|
[1, 2, 3, 4, 5]
|
|
|
|
|
],
|
|
|
|
|
"fully multiline": [
|
|
|
|
|
[
|
|
|
|
|
1,
|
|
|
|
|
2,
|
|
|
|
|
3
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
})"));
|
|
|
|
|
}
|
2018-06-05 05:59:21 +03:00
|
|
|
|
|
|
|
|
SECTION("example of more sophisticated json matcher")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_stylizer stylizer;
|
|
|
|
|
stylizer.get_default_style() = print_style::preset_multiline;
|
2018-06-05 05:59:21 +03:00
|
|
|
|
Refactor: rename the register_style overloads
I really really wanted to name these the same and overload them,
but I couldn't get the metaprogramming to work right. Here's a
comment I wrote that describes the problems and what I *planned*
to do:
// What we want is the register_style overloads below. I chose to
// keep them with the same name. But there are two problems with
// that. First, because I need to wrap them in a std::function
// when promoting to two arguments, I want to make register_style
// themselves take the function parameter by a template argument
// so it doesn't get type-erased "twice" (with two virtual
// calls). But then that means that both versions would have the
// generic signature "template <typename Predicate>
// ... (Predicate, style)" and that would lead to ambiguous calls.
//
// The second problem is that ever if you keep the first parameter
// a std::function, because 'json_pointer' is implicitly
// convertible to a 'json', if you rely on the implicit conversion
// to std::function then you'd get an ambugious call.
//
// So what we want is, using Concept terms:
//
// template <JsonCallable Predicate> ... (Predicate, style)
// template <JsonPointerCallable Predicate> ... (Predicate, style)
//
// where JsonCallable is additionally *not*
// JsonPointerCallable. The following is my attempt to get that.
I then wrote some code that is similar to this:
#include <functional>
struct Main {};
struct Secondary { Secondary(Main); };
// http://en.cppreference.com/w/cpp/types/void_t
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
template <typename, typename = void>
struct can_be_called_with_main : std::false_type { };
template <typename T>
struct can_be_called_with_main<
T,
void_t<decltype(std::declval<T>()(std::declval<Main>()))>
>: std::true_type { };
template <typename, typename = void>
struct can_be_called_with_secondary : std::false_type { };
template <typename T>
struct can_be_called_with_secondary<
T,
void_t<decltype(std::declval<T>()(std::declval<Secondary>()))>
>: std::true_type { };
template <typename Functor>
auto
func(Functor f)
-> typename std::enable_if<can_be_called_with_main<Functor>::value, int>::type
{
return 0;
}
template <typename Functor>
auto
func(Functor f)
-> typename std::enable_if<
can_be_called_with_secondary<Functor>::value
&& !can_be_called_with_main<Functor>::value
, int>::type
{
return 0;
}
auto x1 = func([] (Main) {});
auto x2 = func([] (Secondary) {});
where Main is like 'json' and Secondary like 'json_pointer'.
Problem is it doesn't work -- in the SFIANE context, it looks like
predicates of both `bool (json)` and `bool (json_pointer)` are callable
with both json and json_pointer objects.
In the case of `bool (json)` being called with a 'json_pointer', that
is via the implicit conversion discussed in the comment above. In the
caes of `bool (json_pointer)` being called with a `json`, my guess
as to what is going on is that `json` provides an implicit to-anything
conversion, which uses a `from_json` function. However, that isn't
implemented in a SFIANE-friendly way -- when you try to actually make
that conversion, there's a static_assert failure.
An alternative approach would be to extract the first argument to
the provided predicate via some technique like those described in
https://functionalcpp.wordpress.com/2013/08/05/function-traits/,
and then is_same them vs json and json_pointer.
2018-06-05 06:28:58 +03:00
|
|
|
stylizer.register_style_object_pred(
|
|
|
|
|
[] (const json & j)
|
2018-06-05 05:59:21 +03:00
|
|
|
{
|
|
|
|
|
return j.type() == json::value_t::array;
|
Refactor: rename the register_style overloads
I really really wanted to name these the same and overload them,
but I couldn't get the metaprogramming to work right. Here's a
comment I wrote that describes the problems and what I *planned*
to do:
// What we want is the register_style overloads below. I chose to
// keep them with the same name. But there are two problems with
// that. First, because I need to wrap them in a std::function
// when promoting to two arguments, I want to make register_style
// themselves take the function parameter by a template argument
// so it doesn't get type-erased "twice" (with two virtual
// calls). But then that means that both versions would have the
// generic signature "template <typename Predicate>
// ... (Predicate, style)" and that would lead to ambiguous calls.
//
// The second problem is that ever if you keep the first parameter
// a std::function, because 'json_pointer' is implicitly
// convertible to a 'json', if you rely on the implicit conversion
// to std::function then you'd get an ambugious call.
//
// So what we want is, using Concept terms:
//
// template <JsonCallable Predicate> ... (Predicate, style)
// template <JsonPointerCallable Predicate> ... (Predicate, style)
//
// where JsonCallable is additionally *not*
// JsonPointerCallable. The following is my attempt to get that.
I then wrote some code that is similar to this:
#include <functional>
struct Main {};
struct Secondary { Secondary(Main); };
// http://en.cppreference.com/w/cpp/types/void_t
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
template <typename, typename = void>
struct can_be_called_with_main : std::false_type { };
template <typename T>
struct can_be_called_with_main<
T,
void_t<decltype(std::declval<T>()(std::declval<Main>()))>
>: std::true_type { };
template <typename, typename = void>
struct can_be_called_with_secondary : std::false_type { };
template <typename T>
struct can_be_called_with_secondary<
T,
void_t<decltype(std::declval<T>()(std::declval<Secondary>()))>
>: std::true_type { };
template <typename Functor>
auto
func(Functor f)
-> typename std::enable_if<can_be_called_with_main<Functor>::value, int>::type
{
return 0;
}
template <typename Functor>
auto
func(Functor f)
-> typename std::enable_if<
can_be_called_with_secondary<Functor>::value
&& !can_be_called_with_main<Functor>::value
, int>::type
{
return 0;
}
auto x1 = func([] (Main) {});
auto x2 = func([] (Secondary) {});
where Main is like 'json' and Secondary like 'json_pointer'.
Problem is it doesn't work -- in the SFIANE context, it looks like
predicates of both `bool (json)` and `bool (json_pointer)` are callable
with both json and json_pointer objects.
In the case of `bool (json)` being called with a 'json_pointer', that
is via the implicit conversion discussed in the comment above. In the
caes of `bool (json_pointer)` being called with a `json`, my guess
as to what is going on is that `json` provides an implicit to-anything
conversion, which uses a `from_json` function. However, that isn't
implemented in a SFIANE-friendly way -- when you try to actually make
that conversion, there's a static_assert failure.
An alternative approach would be to extract the first argument to
the provided predicate via some technique like those described in
https://functionalcpp.wordpress.com/2013/08/05/function-traits/,
and then is_same them vs json and json_pointer.
2018-06-05 06:28:58 +03:00
|
|
|
}
|
2018-06-05 06:41:11 +03:00
|
|
|
) = print_style::preset_one_line;
|
2018-06-05 05:59:21 +03:00
|
|
|
|
|
|
|
|
auto str = fancy_to_string(
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
"an array", {1, 2, 3}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"an object", {{"key", "val"}}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
stylizer);
|
|
|
|
|
|
|
|
|
|
CHECK(str == dedent(R"(
|
|
|
|
|
{
|
|
|
|
|
"an array": [1, 2, 3],
|
|
|
|
|
"an object": {
|
|
|
|
|
"key": "val"
|
|
|
|
|
}
|
|
|
|
|
})"));
|
|
|
|
|
}
|
2018-06-03 07:10:28 +03:00
|
|
|
}
|
|
|
|
|
|
2018-06-03 08:00:12 +03:00
|
|
|
SECTION("Spaces after commas are controllable separately from multiline")
|
|
|
|
|
{
|
2018-06-03 08:02:30 +03:00
|
|
|
SECTION("commas")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style;
|
2018-06-03 08:02:30 +03:00
|
|
|
style.space_after_comma = true;
|
|
|
|
|
auto str = fancy_to_string({1, 2, 3}, style);
|
|
|
|
|
CHECK(str == "[1, 2, 3]");
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-03 08:00:12 +03:00
|
|
|
SECTION("colons")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style;
|
2018-06-03 08:00:12 +03:00
|
|
|
style.space_after_colon = true;
|
|
|
|
|
auto str = fancy_to_string({{"one", 1}}, style);
|
|
|
|
|
CHECK(str == "{\"one\": 1}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("multiline can have no space")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style = print_style::preset_multiline;
|
2018-06-03 08:00:12 +03:00
|
|
|
style.space_after_colon = false;
|
|
|
|
|
auto str = fancy_to_string({{"one", 1}}, style);
|
|
|
|
|
CHECK(str == dedent(R"(
|
|
|
|
|
{
|
|
|
|
|
"one":1
|
|
|
|
|
})"));
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-02 09:07:25 +03:00
|
|
|
SECTION("given width")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style = print_style::preset_multiline;
|
2018-06-02 09:18:55 +03:00
|
|
|
auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style);
|
2018-06-03 06:22:24 +03:00
|
|
|
CHECK(str == dedent(R"(
|
|
|
|
|
[
|
|
|
|
|
"foo",
|
|
|
|
|
1,
|
|
|
|
|
2,
|
|
|
|
|
3,
|
|
|
|
|
false,
|
|
|
|
|
{
|
|
|
|
|
"one": 1
|
|
|
|
|
}
|
|
|
|
|
])"));
|
2018-06-02 09:07:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("given fill")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style = print_style::preset_multiline;
|
2018-06-02 09:18:55 +03:00
|
|
|
style.indent_step = 1;
|
|
|
|
|
style.indent_char = '\t';
|
|
|
|
|
|
|
|
|
|
auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style);
|
2018-06-02 09:07:25 +03:00
|
|
|
CHECK(str ==
|
|
|
|
|
"[\n"
|
|
|
|
|
"\t\"foo\",\n"
|
|
|
|
|
"\t1,\n"
|
|
|
|
|
"\t2,\n"
|
|
|
|
|
"\t3,\n"
|
|
|
|
|
"\tfalse,\n"
|
|
|
|
|
"\t{\n"
|
|
|
|
|
"\t\t\"one\": 1\n"
|
|
|
|
|
"\t}\n"
|
|
|
|
|
"]"
|
|
|
|
|
);
|
|
|
|
|
}
|
2018-06-03 05:34:55 +03:00
|
|
|
|
|
|
|
|
SECTION("indent_char is honored for deep indents in lists")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style = print_style::preset_multiline;
|
2018-06-03 05:34:55 +03:00
|
|
|
style.indent_step = 300;
|
|
|
|
|
style.indent_char = 'X';
|
|
|
|
|
|
|
|
|
|
auto str = fancy_to_string({1, {1}}, style);
|
|
|
|
|
|
|
|
|
|
std::string indent(300, 'X');
|
|
|
|
|
CHECK(str ==
|
|
|
|
|
"[\n" +
|
|
|
|
|
indent + "1,\n" +
|
|
|
|
|
indent + "[\n" +
|
|
|
|
|
indent + indent + "1\n" +
|
|
|
|
|
indent + "]\n" +
|
|
|
|
|
"]");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SECTION("indent_char is honored for deep indents in objects")
|
|
|
|
|
{
|
2018-06-05 06:41:11 +03:00
|
|
|
print_style style = print_style::preset_multiline;
|
2018-06-03 05:34:55 +03:00
|
|
|
style.indent_step = 300;
|
|
|
|
|
style.indent_char = 'X';
|
|
|
|
|
|
|
|
|
|
auto str = fancy_to_string({{"key", {{"key", 1}}}}, style);
|
|
|
|
|
|
|
|
|
|
std::string indent(300, 'X');
|
|
|
|
|
CHECK(str ==
|
|
|
|
|
"{\n" +
|
|
|
|
|
indent + "\"key\": {\n" +
|
|
|
|
|
indent + indent + "\"key\": 1\n" +
|
|
|
|
|
indent + "}\n" +
|
|
|
|
|
"}");
|
|
|
|
|
}
|
2018-06-02 06:21:27 +03:00
|
|
|
}
|