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 }