1 module toml_foolery.encode.types..string;
2 
3 import std.algorithm.iteration : substitute, map;
4 import std.array : join;
5 import std.conv : to;
6 import std.format : format;
7 import std.traits : isSomeChar, isSomeString;
8 import std.uni : isControl;
9 
10 import toml_foolery.encode;
11 
12 
13 package(toml_foolery.encode) enum bool makesTomlString(T) = (
14     isSomeChar!T || isSomeString!T
15 );
16 
17 /// Serializes (w/d/)strings and (w/d/)chars into TOML string values, quoted and escaped.
18 package(toml_foolery.encode) void tomlifyValueImpl(T)(
19     const T value,
20     ref Appender!string buffer,
21     immutable string[] parentTables
22 )
23 if (makesTomlString!T)
24 {
25     buffer.put(`"`);
26     buffer.put(value.to!string.escaped);
27     buffer.put(`"`);
28 }
29 
30 private string escaped(string s)
31 {
32     return s.substitute!(
33         "\"", `\"`,
34         "\\", `\\`,
35         "\b", `\b`,
36         "\f", `\f`,
37         "\n", `\n`,
38         "\r", `\r`,
39     ).map!((e)
40     {
41         if (isControl(e) && e != dchar('\t'))
42         {
43             return `\u%04X`.format(cast(long)e);
44         }
45         else
46         {
47             return e.to!string;
48         }
49     }).join;
50 }
51 
52 @("Encode `string` values")
53 unittest
54 {
55     string str = "Eskarina";
56     expect(_tomlifyValue(str)).toEqual(`"Eskarina"`);
57 }
58 
59 @("Encode `wstring` values")
60 unittest
61 {
62     wstring wstr = "Weskarina";
63     expect(_tomlifyValue(wstr)).toEqual(`"Weskarina"`);
64 }
65 
66 @("Encode `dstring` values")
67 unittest
68 {
69     dstring dstr = "Deskarina";
70     expect(_tomlifyValue(dstr)).toEqual(`"Deskarina"`);
71 }
72 
73 @("Encode strings with multi-codepoint unicode characters")
74 unittest
75 {
76     string a = "🍕👨‍👩‍👧🜀";
77     expect(_tomlifyValue(a)).toEqual(`"🍕👨‍👩‍👧🜀"`);
78 
79     wstring b = "👨‍👩‍👧🜀🍕"w;
80     expect(_tomlifyValue(b)).toEqual(`"👨‍👩‍👧🜀🍕"`);
81 
82     dstring c = "🜀🍕👨‍👩‍👧"d;
83     expect(_tomlifyValue(c)).toEqual(`"🜀🍕👨‍👩‍👧"`);
84 }
85 
86 @("Encode `char` values as Strings")
87 unittest
88 {
89     char c = '*';
90     expect(_tomlifyValue(c)).toEqual(`"*"`);
91 }
92 
93 @("Encode `wchar` values as Strings")
94 unittest
95 {
96     wchar w = 'ⵖ';
97     expect(_tomlifyValue(w)).toEqual(`"ⵖ"`);
98 }
99 
100 @("Encode `dchar` values as Strings")
101 unittest
102 {
103     dchar d = '🌻';
104     expect(_tomlifyValue(d)).toEqual(`"🌻"`);
105 }
106 
107 @("Escape characters that need to be escaped")
108 unittest
109 {
110     /++
111      + From the TOML readme:
112      +
113      +
114      + Any Unicode character may be used except those that must be escaped:
115      + quotation mark, backslash, and the control characters other than tab
116      + (U+0000 to U+0008, U+000A to U+001F, U+007F).
117      +
118      + For convenience, some popular characters have a compact escape sequence.
119      +  \b         - backspace       (U+0008)
120      +  \t         - tab             (U+0009)
121      +  \n         - linefeed        (U+000A)
122      +  \f         - form feed       (U+000C)
123      +  \r         - carriage return (U+000D)
124      +  \"         - quote           (U+0022)
125      +  \\         - backslash       (U+005C)
126      +  \uXXXX     - unicode         (U+XXXX)
127      +  \UXXXXXXXX - unicode         (U+XXXXXXXX)
128      +/
129 
130     string compactSequences = "\"\\\b\f\n\r";
131     expect(_tomlifyValue(compactSequences)).toEqual(`"\"\\\b\f\n\r"`);
132 
133     string nonCompactSequences = "\u0001\U0000007f\x00";
134     expect(_tomlifyValue(nonCompactSequences)).toEqual(`"\u0001\u007F\u0000"`);
135 
136     string dontEscapeTab = "\t";
137     expect(_tomlifyValue(dontEscapeTab)).toEqual("\"\t\"");
138 }