1 module handlebars.properties;
2 
3 import std.string;
4 import std.traits;
5 import std.conv;
6 
7 version(unittest) {
8   import fluent.asserts;
9 }
10 
11 ///
12 struct Properties {
13   struct Property {
14     string value;
15     bool isEvaluated;
16 
17     this(string value, bool isEvaluated) {
18       this.value = value;
19       this.isEvaluated = isEvaluated;
20     }
21 
22     this(string value) {
23       if(value == "") {
24         return;
25       }
26 
27       if(value[0] == '"' || value[0] == '\'') {
28         this.value = value[1..$-1];
29         this.isEvaluated = true;
30         return;
31       }
32 
33       if(isNumeric(value)) {
34         this.value = value;
35         this.isEvaluated = true;
36         return;
37       }
38 
39       if(value == "true" || value == "false") {
40         this.value = value;
41         this.isEvaluated = true;
42         return;
43       }
44 
45       this.value = value;
46       this.isEvaluated = false;
47     }
48 
49     T get(T)() if (is(T == string)) {
50       return value;
51     }
52 
53     T get(T)() if (isBuiltinType!T && !is(T == string)) {
54       static if(__traits(compiles, this.value.to!T)) {
55         return this.value.to!T;
56       } else {
57         throw new Exception("Can't get `" ~ value ~ "` as `" ~ T.stringof ~ "`");
58       }
59     }
60 
61     ///
62     string toParam() {
63       if(isEvaluated) {
64         if(value == "true" || value == "false") {
65           return value;
66         }
67 
68         return `"` ~ value ~ `"`;
69       }
70 
71       return "controller." ~ value;
72     }
73   }
74 
75   ///
76   Property[] list;
77 
78   ///
79   Property[string] hash;
80 
81   string localName;
82   string indexName;
83   string name;
84 
85   this(string value) {
86     while(value.length > 0) {
87       auto pos = value.breakIndex;
88       auto item = value[0..pos];
89       auto eqPos = item.indexOf("=");
90 
91       if(eqPos == -1) {
92         list ~= Property(item);
93       } else {
94         auto key = item[0..eqPos];
95         auto val = item[eqPos+1..$];
96 
97         hash[key] = Property(val);
98       }
99 
100       value = value[pos..$].strip;
101 
102     }
103 
104     if(list.length == 3 && !list[0].isEvaluated && list[1].value == "as" && list[2].value[0] == '|') {
105       name = list[0].value;
106       auto localProperties = Properties(list[2].value.replace("|", "").strip);
107       localName = localProperties.list[0].value;
108 
109       if(localProperties.list.length == 2) {
110         indexName = localProperties.list[1].value;
111       }
112 
113       list = [];
114     }
115   }
116 }
117 
118 /// parse a list of string properties
119 unittest {
120   Properties(`"a"  "b"`).list.should.equal([
121     Properties.Property("a", true),
122     Properties.Property("b", true)]);
123 }
124 
125 /// parse a list of string with spaces properties
126 unittest {
127   Properties(`"a b c"  "c d e"`).list.should.equal([
128     Properties.Property("a b c", true),
129     Properties.Property("c d e", true)]);
130 }
131 
132 /// parse a list of variables with spaces properties
133 unittest {
134   Properties(`a 1 c  "c d e"`).list.should.equal([
135     Properties.Property("a", false),
136     Properties.Property("1", true),
137     Properties.Property("c", false),
138     Properties.Property("c d e", true)]);
139 }
140 
141 /// parse a list of attributes with spaces properties
142 unittest {
143   Properties(`a=1 b=value`).hash["a"]
144     .should.equal(Properties.Property("1", true));
145 
146   Properties(`a=1 b=value`).hash["b"]
147     .should.equal(Properties.Property("value", false));
148 }
149 
150 /// parse each properties
151 unittest {
152   Properties(`list as |item|`).name.should.equal("list");
153   Properties(`list as |item|`).localName.should.equal("item");
154   Properties(`list as |item|`).indexName.should.equal("");
155 
156 
157   Properties(`list as | item index |`).name.should.equal("list");
158   Properties(`list as | item index |`).localName.should.equal("item");
159   Properties(`list as | item index |`).indexName.should.equal("index");
160 }
161 
162 ///
163 size_t breakIndex(string value) {
164   size_t pos = value.length;
165 
166   if(value.length == 0) {
167     return 0;
168   }
169 
170   if(value[0] == '"' || value[0] == '\'' || value[0] == '|') {
171     auto tmp = value[1..$];
172 
173     while(tmp.length > 0) {
174       auto endPos = tmp.indexOf(value[0]);
175 
176       if(tmp.length > 1 && tmp[endPos - 1] != '\\') {
177         tmp = tmp[endPos+1..$];
178         break;
179       }
180 
181       tmp = tmp[endPos+1..$];
182     }
183 
184     return value.length - tmp.length;
185   }
186 
187   foreach(token; [ " ", "\t", "\n" ]) {
188     auto tmp = value.indexOf(token) ;
189 
190     if(tmp < pos) {
191       pos = tmp;
192     }
193   }
194 
195   return pos;
196 }
197 
198 /// break index with no breaks should return the length of the string
199 unittest {
200   "".breakIndex.should.equal(0);
201   "abc".breakIndex.should.equal(3);
202 }
203 
204 /// break index with one break should return the position of the next item
205 unittest {
206   "a b".breakIndex.should.equal(1);
207   "a\tb".breakIndex.should.equal(1);
208   "a\nb".breakIndex.should.equal(1);
209   `"abc" "def"`.breakIndex.should.equal(5);
210   `"a c" "def"`.breakIndex.should.equal(5);
211   `'a c' "def"`.breakIndex.should.equal(5);
212 }
213 
214 /// break index with escaped strings
215 unittest {
216   `"a\"c" "def"`.breakIndex.should.equal(6);
217   `'a\'c' "def"`.breakIndex.should.equal(6);
218 }
219 
220 /// break index with bar string
221 unittest {
222   `| a  b |`.breakIndex.should.equal(8);
223 }