1 // Written in the D programming language.
2 /**
3 Haystack trio encoder.
4 
5 Copyright: Copyright (c) 2019, Radu Racariu <radu.racariu@gmail.com>
6 License:   $(LINK2 www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7 Authors:   Radu Racariu
8 **/
9 module haystack.trio.encode;
10 import std.range.primitives : isOutputRange;
11 import haystack.tag;
12 import haystack.zinc.encode : toZinc = encode;
13 import haystack.util.tzdata : timeZone;
14 public import haystack.zinc.encode : SortedKeys;
15 
16 /**
17 Encodes `Dict`as a Trio.
18 Expects an OutputRange as writer.
19 Returns: the writter OutputRange
20 */
21 ref R encode(R) (auto ref R writer, const(Dict) dict, SortedKeys sorted = SortedKeys.no)
22 if (isOutputRange!(R, char))
23 {
24     import std.algorithm    : each, sort;
25     
26     void encodeKeyValue(string key)
27     {
28         writer.put(key);
29         if (dict[key].hasValue!Marker)
30         {
31             writer.put("\n");
32             return;
33         }
34 
35         writer.put(':');
36 
37         if (dict[key].hasValue!XStr)
38         {
39             const xstr = dict.get!XStr(key);
40             writer.put(xstr.type);
41             writer.put(":\n  ");
42             auto padder = PaddingOutput!R(writer);
43             padder.put(xstr.val);
44             if (padder.lastChar == '\n')
45                 return;
46         }
47         else if (dict[key].hasValue!Grid)
48         {
49             writer.put("Zinc");
50             writer.put(":\n  ");
51             auto padder = PaddingOutput!R(writer);
52             const grid = dict.get!Grid(key); 
53             toZinc(grid, padder, sorted);
54         }
55         else if (dict[key].hasValue!Dict)
56         {
57             toZinc(dict[key], writer, sorted);
58         }
59         else
60         {
61             toZinc(dict[key], writer);
62         }
63         writer.put("\n");
64     }
65 
66     if (sorted == SortedKeys.no)
67         dict.byKey.each!(k => encodeKeyValue(k));
68    else
69         dict.keys.sort.each!(k => encodeKeyValue(k));
70     
71     return writer;
72 }
73 
74 /**
75 Encodes Dict to a Trio string
76 */
77 string trio(const(Dict) dict, SortedKeys sorted = SortedKeys.no)
78 {
79     import std.array : appender;
80     auto buf = appender!string();
81     buf.encode(dict, sorted);
82     return buf.data();
83 }
84 unittest
85 {
86     assert(trio(["marker":marker()]) == "marker\n");
87 }
88 
89 unittest
90 {   
91     string expected =q"{bool:F
92 coord:C(37.545826,-77.449188)
93 date:2019-06-14
94 dateTime:2019-06-14T15:24:00+09:00 Tokyo
95 marker
96 na:NA
97 number:42$
98 ref:@someId
99 str:"a string"
100 time:16:23:03
101 uri:`/a/b/c`
102 xstr:XStr:
103   xstr content
104   foo
105 }";
106 
107     Dict dict =
108     [
109         "bool":     false.tag,
110         "coord":    Coord(37.545826,-77.449188).tag,
111         "date":     Date(2019, 6, 14).tag,
112         "dateTime": SysTime(DateTime(Date(2019, 6, 14), TimeOfDay(15, 24, 0)), timeZone("Asia/Tokyo")).tag,
113         "marker":   marker,
114         "na":       na,
115         "number":   Num(42, "$").tag,
116         "ref":      Ref("someId").tag,
117         "str":      "a string".tag,
118         "time":     Time(16, 23, 3).tag,
119         "uri":      Uri(`/a/b/c`).tag,
120         "xstr":     XStr("XStr", "xstr content\nfoo").tag,
121     ];
122     assert(trio(dict, SortedKeys.yes) == expected);
123 }
124 unittest
125 {   
126     string expected =q"{dict:{a:"a" b:1}
127 list:[1,2,"3"]
128 zinc:Zinc:
129   ver:"3.0"
130   empty
131   
132 }";
133 
134     Dict dict =
135     [
136         "dict": ["a": "a".tag, "b": 1.tag].tag,
137         "list": [1.tag, 2.tag, "3".tag].tag,
138         "zinc": Grid().tag,
139     ];
140     assert(trio(dict, SortedKeys.yes) == expected);
141 }
142 
143 // Adds space padding after each line
144 private struct PaddingOutput(R)
145 if (isOutputRange!(R, char))
146 {
147     this (scope ref R r) scope
148     {
149         this.r = &r;
150     }
151 
152     void put(string str)
153     {
154         foreach (c; str)
155             put(c);
156     }
157 
158     void put(dchar c)
159     {
160         if (c != '\n')
161             r.put(c);
162         else
163             r.put("\n  ");
164 
165         lastChar = c;
166     }
167 
168     R* r;
169     dchar lastChar;
170 }