Mercurial > projects > dynamin
comparison dynamin/core/format.d @ 109:7678554e75de
Add dynamin.core.format with parseFormatString().
author | Jordan Miner <jminer7@gmail.com> |
---|---|
date | Sat, 19 Jan 2013 20:50:41 -0600 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
108:b3091e460553 | 109:7678554e75de |
---|---|
1 | |
2 /* | |
3 * Copyright Jordan Miner | |
4 * | |
5 * Distributed under the Boost Software License, Version 1.0. | |
6 * (See accompanying file BOOST_LICENSE.txt or copy at | |
7 * http://www.boost.org/LICENSE_1_0.txt) | |
8 * | |
9 */ | |
10 | |
11 module dynamin.core.format; | |
12 | |
13 import dynamin.core.global; | |
14 import dynamin.core.string; | |
15 import dynamin.core.test; | |
16 | |
17 private: | |
18 | |
19 cstring parseFormatParam(cstring specifier, ref int paramIndex) { | |
20 // specifier is something like "1:x" or ":d" | |
21 int i; | |
22 for(i = 0; i < specifier.length && specifier[i] != ':'; ++i) { | |
23 } | |
24 if(i > 0) { | |
25 try { | |
26 paramIndex = to!int(specifier[0..i]); | |
27 } catch(Exception ex) { | |
28 throw new Exception("Invalid parameter index."); | |
29 } | |
30 } | |
31 return i >= specifier.length ? "" : specifier[i+1..$]; | |
32 } | |
33 | |
34 /** | |
35 * Parses `formatStr`, passing each part to `write` or `writeParam` as it is encountered. | |
36 * For example: | |
37 * | |
38 * parseFormatString("I am {0:x} years old!", writeParam, write); | |
39 * // will result in: | |
40 * write("I am "); | |
41 * writeParam(0, "x"); | |
42 * write(" years old!"); | |
43 * | |
44 */ | |
45 public void parseFormatString( cstring formatStr, | |
46 void delegate(int param, cstring format) writeParam, | |
47 void delegate(scope cstring str) write) { | |
48 | |
49 int paramIndex = 0; // param to write next, if not specified | |
50 cstring paramFormat; | |
51 | |
52 uword start = 0; | |
53 | |
54 for(int i = 0; i < formatStr.length; ++i) { | |
55 if(formatStr[i] == '{') { | |
56 write(formatStr[start..i]); | |
57 start = i + 1; | |
58 | |
59 if(i + 1 >= formatStr.length) | |
60 throw new Exception("Unterminated format specifier."); | |
61 ++i; | |
62 | |
63 if(formatStr[i] == '{') { // escaped open brace | |
64 // start already is the index of the second open brace | |
65 } else { // format specifier | |
66 while(formatStr[i] != '}') { | |
67 if(formatStr[i] == '{') | |
68 throw new Exception("Open brace in format specifier."); | |
69 if(++i >= formatStr.length) | |
70 throw new Exception("Unterminated format specifier."); | |
71 } | |
72 paramFormat = parseFormatParam(formatStr[start..i], paramIndex); | |
73 start = i + 1; | |
74 writeParam(paramIndex, paramFormat); | |
75 ++paramIndex; | |
76 } | |
77 } else if(formatStr[i] == '}') { | |
78 write(formatStr[start..i]); | |
79 start = i + 1; | |
80 | |
81 if(i + 1 >= formatStr.length || formatStr[i + 1] != '}') | |
82 throw new Exception("Unescaped close brace."); | |
83 ++i; // escaped close brace | |
84 } | |
85 } | |
86 if(start < formatStr.length) | |
87 write(formatStr[start..$]); | |
88 } | |
89 | |
90 unittest { | |
91 struct Call { | |
92 int index; | |
93 string str; | |
94 } | |
95 Call[] calls; | |
96 int callIndex; | |
97 void paramCallback(int param, cstring format) { | |
98 assertEqual(param, calls[callIndex].index); // crash if out of bounds | |
99 assertEqual(format, calls[callIndex].str); | |
100 ++callIndex; | |
101 }; | |
102 void strCallback(scope cstring str) { | |
103 assertEqual(str, calls[callIndex].str); // crash if out of bounds | |
104 ++callIndex; | |
105 }; | |
106 | |
107 calls = [ | |
108 Call(-1, "one"), | |
109 Call(0, ""), | |
110 Call(-1, "two"), | |
111 Call(1, ""), | |
112 Call(-1, "three") | |
113 ]; | |
114 callIndex = 0; | |
115 parseFormatString("one{0}two{1}three", ¶mCallback, &strCallback); | |
116 assertEqual(callIndex, calls.length); | |
117 | |
118 calls = [ | |
119 Call(-1, "one"), | |
120 Call(0, "abc12"), | |
121 Call(-1, "two"), | |
122 Call(1, "yyyy-mm-dd"), | |
123 Call(-1, "three") | |
124 ]; | |
125 callIndex = 0; | |
126 parseFormatString("one{0:abc12}two{1:yyyy-mm-dd}three", ¶mCallback, &strCallback); | |
127 assertEqual(callIndex, calls.length); | |
128 | |
129 calls = [ | |
130 Call(-1, "one"), | |
131 Call(0, "~!@#$%^&*()_+-=[]\\:\"<>?;',./"), | |
132 Call(-1, "two"), | |
133 ]; | |
134 callIndex = 0; | |
135 parseFormatString("one{0:~!@#$%^&*()_+-=[]\\:\"<>?;',./}two", ¶mCallback, &strCallback); | |
136 assertEqual(callIndex, calls.length); | |
137 | |
138 calls = [ | |
139 Call(-1, "one"), | |
140 Call(0, ""), | |
141 Call(-1, "two"), | |
142 Call(1, ""), | |
143 Call(-1, "three"), | |
144 Call(2, ""), | |
145 ]; | |
146 callIndex = 0; | |
147 parseFormatString("one{}two{}three{}", ¶mCallback, &strCallback); | |
148 assertEqual(callIndex, calls.length); | |
149 | |
150 calls = [ | |
151 Call(-1, "one"), | |
152 Call(4, ""), | |
153 Call(-1, "two"), | |
154 Call(5, ""), | |
155 Call(-1, "three"), | |
156 Call(2, ""), | |
157 Call(-1, "four"), | |
158 Call(3, "x"), | |
159 ]; | |
160 callIndex = 0; | |
161 parseFormatString("one{4}two{}three{2}four{:x}", ¶mCallback, &strCallback); | |
162 assertEqual(callIndex, calls.length); | |
163 | |
164 string testStr; | |
165 parseFormatString( "on{{e}}{2}}}two{{{}}}", | |
166 delegate (i, f) { testStr ~= "P"; }, | |
167 delegate (scope str) { testStr ~= str; }); | |
168 assertEqual(testStr, "on{e}P}two{P}"); | |
169 | |
170 assertThrows!Exception(parseFormatString( "test{", | |
171 delegate (i, f) { }, | |
172 delegate (scope str) { })); | |
173 assertThrows!Exception(parseFormatString( "test{0", | |
174 delegate (i, f) { }, | |
175 delegate (scope str) { })); | |
176 assertThrows!Exception(parseFormatString( "test{0{}", | |
177 delegate (i, f) { }, | |
178 delegate (scope str) { })); | |
179 assertThrows!Exception(parseFormatString( "test}ing", | |
180 delegate (i, f) { }, | |
181 delegate (scope str) { })); | |
182 assertThrows!Exception(parseFormatString( "test{0x12}ing", | |
183 delegate (i, f) { }, | |
184 delegate (scope str) { })); | |
185 } | |
186 |