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