1 // Copyright © 2013, Peter Wood.
2
3 module properd;
4
5 import std.conv, std.file, std..string;
6 import std.stdio : File;
7 import std.algorithm : map;
8 import std.array;
9
10 /**
11 * An exception class for the errors generated by the proper-d code.
12 */
13 class PropertyException : Exception {
14 this(string message, Throwable thrown=null) {
15 super(message, thrown);
16 }
17 }
18
19 /**
20 * This function reads in the contents of a file before passing it to the
21 * parseProperties() function for processing.
22 *
23 * Returns: The associative array returned by parseProperties().
24 *
25 * Params:
26 * path = A string containing the path and name of the file to be read.
27 */
28 string[string] readProperties(string path) {
29 if(!exists(path)) {
30 throw(new PropertyException(text("The '", path, "' file does not exist.")));
31 }
32 return(parseProperties(readText(path)));
33 }
34
35 /**
36 * This function reads in the contents of a file before passing it to the
37 * parseProperties() function for processing.
38 *
39 * Returns: The associative array returned by parseProperties().
40 *
41 * Params:
42 * path = A string containing the path and name of the file to be read.
43 */
44 string[string] readProperties(File* file) {
45 return(readProperties(file.name));
46 }
47
48 /**
49 * This function takes a string of text and attempts to interpret it using the
50 * following set of rules...
51 *
52 * 1. Blank lines are ignored.
53 * 2. Lines that start (after the removal of leading whitespace) with a '#'
54 * are ignored.
55 * 3. All other lines will first be stripped of leading and trailing
56 * whitespace then searched for a '=' and, if that is not found, a ':'.
57 * If neither of these is found an exception is thrown. If one of them is
58 * found then the line is split around the first occurrence with the part
59 * of the left hand side becoming a key and the part on the right hand
60 * side become a value in the associative array generated.
61 *
62 * Returns: An associative array of strings indexed by strings.
63 *
64 * Params:
65 * input = The string containing the text to be parsed.
66 */
67 string[string] parseProperties(string input) {
68 string[string] output;
69
70 if(input.length > 0) {
71 uint count;
72
73 foreach(line; input.splitLines()) {
74 line = line.strip();
75 count++;
76 if(line.length > 0) {
77 if(line[0..1] != "#") {
78 auto index = line.indexOf("=");
79
80 if(index == -1) {
81 index = line.indexOf(":");
82 }
83
84 if(index == -1) {
85 throw(new PropertyException(text("Syntax error on line ", count, " of property data.")));
86 }
87
88 output[line[0..index].strip()] = line[(index + 1)..$].strip();
89 }
90 }
91 }
92 }
93
94 return(output);
95 }
96
97 /**
98 * Convenience function that attempts to extract a property value from a
99 * property list (an associative array of strings indexed by strings) and
100 * convert it to a specified type.
101 *
102 * Params:
103 * properties = The associative array to locate the property in.
104 * name = The name the value is currently keyed under.
105 * alternative = The value to return in the case that the properties list
106 * doesn't contain the named property. Defaults to whatever
107 * the default initialization value for the desired type
108 * is.
109 */
110 T as(T)(string[string] properties, string name, T alternative=T.init) {
111 T result = alternative;
112 string value = (name in properties ? properties[name] : null);
113
114 if(value !is null) {
115 try {
116 result = to!(T)(value);
117 } catch(Exception exception) {
118 throw(new PropertyException(text("Cannot convert the value of the '", name, "' property ('", value, "') to a ", typeid(value), ".")));
119 }
120 }
121
122 return(result);
123 }
124
125 /**
126 * Template specialization of the as() function for generating boolean values.
127 * This function will recognise "true", "1", "yes" and "on" as values that
128 * convert to a boolean true, with everything else converting to false.
129 */
130 bool as(T : bool)(string[string] properties, string name, T alternative=T.init) {
131 bool result = alternative;
132 string value = (name in properties ? properties[name] : null);
133
134 if(value !is null) {
135 value = value.toLower();
136 result = (value == "true" || value == "1" || value == "yes" || value == "on");
137 }
138
139 return(result);
140 }
141
142 /**
143 * Convenience function that attempts to extract a property value from a
144 * property list (an associative array of strings indexed by strings),
145 * split the value with separater, convert each value to a specified type
146 * and return as an array.
147 *
148 * Params:
149 * properties = The associative array to locate the property in.
150 * name = The name the value is currently keyed under.
151 * sep = value separater
152 */
153 T[] asArray(T)(string[string] properties, string name, string sep = ",") {
154 T[] result;
155 string value = (name in properties ? properties[name] : null);
156
157 if(value !is null) {
158 try {
159 result = value.split(sep).map!strip.map!(to!(T)).array;
160 } catch(Exception exception) {
161 throw(new PropertyException(text("Cannot convert the value of the '", name, "' property ('", value, "') to a ", typeid(value), ".")));
162 }
163 }
164
165 return(result);
166 }
167