Skip to content

Commit

Permalink
Merge pull request #1 from WebFreak001/serializer
Browse files Browse the repository at this point in the history
add toml serializer
  • Loading branch information
o3o authored Jun 28, 2022
2 parents ddbe779 + a15bc7c commit 5cb495c
Show file tree
Hide file tree
Showing 2 changed files with 332 additions and 1 deletion.
331 changes: 331 additions & 0 deletions src/toml/serialize.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
module toml.serialize;

import toml.toml;

import std.array;
import std.sumtype;
import std.traits;

// UDAs:
struct tomlName { string name; }
enum tomlIgnored;
// ---

string serializeTOML(T)(T value)
{
auto ret = appender!string;
serializeTOML(value, ret);
return ret.data;
}

void serializeTOML(T, Output)(T value, ref Output output)
{
serializeTOML(value, output, "", "");
}

private:

// indentation increase per level
enum oneIndentLevel = " ";

template fieldName(alias field)
{
enum nameUDAs = getUDAs!(field, tomlName);
static if (nameUDAs.length == 1)
enum fieldName = nameUDAs[0].name;
else static if (nameUDAs.length == 0)
enum fieldName = __traits(identifier, field);
else
static assert(false, "Field " ~ __traits(identifier, field) ~ " has multiple @tomlName UDAs");
}

void serializeTOML(T, Output)(T value, ref Output output, string indent, string group)
if (isPlainStruct!T && !is(T == MapT[string], MapT) && !is(T == SumType!Types, Types...))
{
foreach (i, ref v; value.tupleof)
{{
static if (isValueSerializable!(typeof(v))
&& getUDAs!(value.tupleof[i], tomlIgnored).length == 0
&& !isStructArray!(typeof(v)))
{
enum prefix = fieldName!(value.tupleof[i]) ~ " = ";
if (indent.length)
output.put(indent);
output.put(prefix);
serializeTOMLValue(v, output);
output.put("\n");
}
}}

foreach (i, ref v; value.tupleof)
{{
static if (isValueSerializable!(typeof(v))
&& getUDAs!(value.tupleof[i], tomlIgnored).length == 0
&& isStructArray!(typeof(v)))
{
enum prefix = fieldName!(value.tupleof[i]) ~ "]]\n";
auto deeperIndent = v.length ? indent ~ oneIndentLevel : null;
auto deepGroup = group ~ (fieldName!(value.tupleof[i]) ~ ".");
foreach (item; v)
{
output.put("\n");
if (indent.length)
output.put(indent);
output.put("[[");
if (group.length)
output.put(group);
output.put(prefix);
serializeTOML(item, output, deeperIndent, deepGroup);
}
}
}}

foreach (i, ref v; value.tupleof)
{{
static if (!isValueSerializable!(typeof(v))
&& getUDAs!(value.tupleof[i], tomlIgnored).length == 0)
{
enum prefix = fieldName!(value.tupleof[i]) ~ "]\n";
output.put("\n");
if (indent.length)
output.put(indent);
output.put("[");
if (group.length)
output.put(group);
output.put(prefix);
serializeTOML(v, output, indent ~ oneIndentLevel, group ~ (fieldName!(value.tupleof[i]) ~ "."));
}
}}
}

void serializeTOML(T, Output)(T value, ref Output output, string indent, string group)
if (is(T == SumType!Types, Types...))
{
if (indent.length)
output.put(indent);
output.put("kind = ");

value.match!(
(part) {
serializeTOMLValue(typeof(part).stringof, output);
output.put("\n");
if (indent.length)
output.put(indent);

static if (isValueSerializable!(typeof(part)))
{
static if (isStructArray!(typeof(part)))
{
auto deeperIndent = indent ~ oneIndentLevel;
auto deepGroup = group ~ "value.";
foreach (arritem; part)
{
output.put("[[");
if (group.length)
output.put(group);
output.put("value]]\n");
serializeTOML(arritem, output, deeperIndent, deepGroup);
}
}
else static if (isValueSerializable!(typeof(part)))
{
output.put("value = ");
serializeTOMLValue(part, output);
output.put("\n");
}
else
{
output.put("[");
if (group.length)
output.put(group);
output.put("value]\n");
serializeTOML(part, output, indent ~ oneIndentLevel, group ~ "value.");
}
}
else
{
output.put("[");
if (group.length)
output.put(group);
output.put("value]\n");
serializeTOML(part, output, indent ~ oneIndentLevel, group ~ "value.");
}
}
);
}

void serializeTOML(T, Output)(T[string] data, ref Output output, string indent, string group)
{
static if (isValueSerializable!T)
{
static if (isStructArray!T)
{
foreach (key, value; data)
{
if (indent.length)
output.put(indent);
output.put(key);
output.put(" = ");
serializeTOMLValue(value, output);
output.put("\n");
}
}
else
{
auto deeperIndent = v.length ? indent ~ oneIndentLevel : null;
foreach (key, value; data)
{
foreach (arritem; value)
{
output.put("\n");
if (indent.length)
output.put(indent);
output.put("[[");
if (group.length)
output.put(group);
output.put(key);
output.put("]]\n");
serializeTOML(arritem, output, deeperIndent, group ~ key ~ ".");
}
}
}
}
else
{
auto deeperIndent = indent ~ oneIndentLevel;
foreach (key, value; data)
{
output.put("\n");
if (indent.length)
output.put(indent);
output.put("[");
if (group.length)
output.put(group);
output.put(key);
output.put("]\n");
serializeTOML(value, output, deeperIndent, group ~ key ~ ".");
}
}
}

// format struct arrays as expanded fields
enum isStructArray(T) = is(T == U[], U) && isPlainStruct!U;

enum isPlainStruct(T) = is(T == struct) || is(T == V[string], V);

enum isValueSerializable(T) = !is(T == struct);

void serializeTOMLValue(T, Output)(T value, ref Output output)
{
static if (__traits(compiles, { auto v = TOMLValue(value); }))
{
auto v = TOMLValue(value);
v.append(output);
}
else
static assert(false, "TODO: serialize value type " ~ T.stringof ~ " not implemented");
}

unittest
{
struct Database
{
string host;
string database;
int port;
}

struct User
{
string name;
@tomlIgnored
string password;
@tomlName("id")
int count;
}

struct Config
{
string token;
Database database;
int[] ports;
User[] users;
}

Config config = {
token: "bot123",
ports: [1337, 4242, 5555],
users: [
User("Alice", "123", 1),
User("Bob", "456", 2),
],
database: Database("localhost", "mybot", 8080)
};

auto str = serializeTOML(config);

assert(str == `token = "bot123"
ports = [1337, 4242, 5555]
[[users]]
name = "Alice"
id = 1
[[users]]
name = "Bob"
id = 2
[database]
host = "localhost"
database = "mybot"
port = 8080
`, str);
}

unittest
{
struct Property
{
SumType!(int, string) id;
SumType!(int, string)[] attributes;
}

Property[string] props = [
"href": Property(
SumType!(int, string)(1),
[SumType!(int, string)(1), SumType!(int, string)("foo")],
),
"base": Property(
SumType!(int, string)("bar"),
[SumType!(int, string)(44)],
)
];

auto str = serializeTOML(props);

assert(str == `
[base]
[[base.attributes]]
kind = "int"
value = 44
[base.id]
kind = "string"
value = "bar"
[href]
[[href.attributes]]
kind = "int"
value = 1
[[href.attributes]]
kind = "string"
value = "foo"
[href.id]
kind = "int"
value = 1
`, str);
}
2 changes: 1 addition & 1 deletion src/toml/toml.d
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ struct TOMLValue {
}
}

public inout void append(ref Appender!string appender) {
public inout void append(Output)(ref Output appender) {
final switch(this._type) with(TOML_TYPE) {
case STRING:
appender.put(formatString(this.store.str));
Expand Down

0 comments on commit 5cb495c

Please sign in to comment.