1 /**
2  * JavaProperties Loader
3  *
4  * Copyright: (c) 2015-2016, Milofon Project.
5  * License: Subject to the terms of the BSD license, as written in the included LICENSE.txt file.
6  * Authors: Maksim Galanin
7  */
8 module proped.loaders.properties;
9 
10 private
11 {
12     import std.algorithm.iteration : map, filter;
13     import std.exception : enforceEx;
14     import std.file : FileException, readText;
15     import std.typecons : Tuple, tuple;
16     import std..string : splitLines;
17     import std.array : front;
18     import std.conv : to;
19 
20     import proped.properties : Properties, PropNode;
21     import proped.loader : PropertiesLoader;
22     import proped.exception : PropertiesException;
23 }
24 
25 
26 /**
27  * The loader data from a .properties file
28  *
29  * Implements PropertiesLoader
30  */
31 class PropertiesPropertiesLoader : PropertiesLoader
32 {
33     /**
34      * Loading properties from a file
35      *
36      * Params:
37      *
38      * fileName = Path to the file system
39      */
40     Properties loadPropertiesFile(string fileName)
41     {
42         try 
43         {
44             string source = readText(fileName);       
45             return loadPropertiesString(source);
46         }
47         catch (FileException e)
48             throw new PropertiesException("Error loading properties from a file '" ~ fileName ~ "':", e);
49     }
50 
51 
52     /**
53      * Loading properties from a string
54      *
55      * Params:
56      *
57      * data = Source string
58      */
59     Properties loadPropertiesString(string data)
60     {
61         return toProperties(data);
62     }
63 
64 
65     private Properties toProperties(string data)
66     {
67         PropNode[string] map;
68         Properties result = Properties(PropNode(map));
69         alias Pair = Tuple!(string, "key", string, "value");
70 
71         Pair parsePair(string line)
72         {
73             size_t keyLen;
74             char c;
75             size_t valueStart = line.length;
76             bool hasSep = false;
77             bool precedingBackslash = false;
78             size_t limit = line.length;
79 
80             while (keyLen < limit)
81             {
82                 c = line[keyLen];
83                 if ((c == '=' || c == ':') && !precedingBackslash)
84                 {
85                     valueStart = keyLen + 1;
86                     hasSep = true;
87                     break;
88                 }
89                 else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash)
90                 {
91                     valueStart = keyLen + 1;
92                     break;
93                 }
94 
95                 if (c == '\\')
96                     precedingBackslash = !precedingBackslash;
97                 else
98                     precedingBackslash = false;
99 
100                 keyLen++;
101             }
102 
103             while (valueStart < limit)
104             {
105                 c = line[valueStart];
106                 if (c != ' ' && c != '\t' && c != '\f')
107                 {
108                     if (!hasSep && (c == '=' || c == ':'))
109                         hasSep = true;
110                     else
111                         break;
112                 }
113                 valueStart++;
114             }
115 
116             return tuple!("key", "value")(line[0..keyLen], line[valueStart..limit]);
117         }
118 
119         auto pairRange = data.splitLines
120             .filter!(l => l.length > 0 && !(l[0] == '#' || l[0] == '!'))
121             .map!(parsePair);
122 
123         foreach(Pair p; pairRange)
124             result.set(p.key, convertValue(p.value));
125 
126         return result;
127     }
128 
129 
130     private PropNode convertValue(string value)
131     {
132         if (value == "false")
133             return PropNode(false);
134         else if (value == "true")
135             return PropNode(true);
136         else if (value == "null")
137             return PropNode();
138         else
139         {
140             switch (value.front)
141             {
142                 case '-':
143                 case '0': .. case '9':
144                     bool is_long_overflow;
145                     bool is_float;
146                     auto num = skipNumber(value, is_float, is_long_overflow);               
147                     if (is_float) 
148                         return PropNode(to!double(num));
149                     else 
150                         return PropNode(to!long(num));
151                 default:
152                     return PropNode(value);
153             }
154         }
155     }
156 
157     /**
158      * Returns the file extension to delermite the type loader
159      */
160     string[] getExtensions()
161     {
162         return [".properties"];
163     }
164 }
165 
166 
167 /**
168  * Parse value
169  * get from vibe.d 
170  */
171 private string skipNumber(R)(ref R s, out bool is_float, out bool is_long_overflow)
172 {
173     size_t idx = 0;
174     is_float = false;
175     is_long_overflow = false;
176     ulong int_part = 0;
177     if (s[idx] == '-') idx++;
178     if (s[idx] == '0') idx++;
179     else {
180         enforceProperties(isDigit(s[idx]), "Digit expected at beginning of number.");
181         int_part = s[idx++] - '0';
182         while( idx < s.length && isDigit(s[idx]) )
183         {
184             if (!is_long_overflow)
185             {
186                 auto dig = s[idx] - '0';
187                 if ((long.max / 10) > int_part || ((long.max / 10) == int_part && (long.max % 10) >= dig))
188                 {
189                     int_part *= 10;
190                     int_part += dig;
191                 }
192                 else
193                 {
194                     is_long_overflow = true;
195                 }
196             }
197             idx++;
198         }
199     }
200 
201     if( idx < s.length && s[idx] == '.' ){
202         idx++;
203         is_float = true;
204         while( idx < s.length && isDigit(s[idx]) ) idx++;
205     }
206 
207     if( idx < s.length && (s[idx] == 'e' || s[idx] == 'E') ){
208         idx++;
209         is_float = true;
210         if( idx < s.length && (s[idx] == '+' || s[idx] == '-') ) idx++;
211         enforceProperties( idx < s.length && isDigit(s[idx]), "Expected exponent." ~ s[0 .. idx]);
212         idx++;
213         while( idx < s.length && isDigit(s[idx]) ) idx++;
214     }
215 
216     string ret = s[0 .. idx];
217     s = s[idx .. $];
218     return ret;
219 }
220 
221 
222 private bool isDigit(dchar ch) @safe nothrow pure 
223 { 
224     return ch >= '0' && ch <= '9'; 
225 }
226 
227 
228 private void enforceProperties(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message = "Properties exception")
229 {
230 	enforceEx!PropertiesParseException(cond, message, file, line);
231 }
232 
233 
234 class PropertiesParseException : Exception
235 {
236     this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
237     {
238         super(msg, file, line, next);
239     }
240 }
241