1 /** 2 * Defines template mixins for defining properties in classes. 3 * 4 * Authors: Colden Cullen, ColdenCullen@gmail.com 5 */ 6 module dash.core.properties; 7 import dash.utility..string; 8 9 public import std.traits; 10 import std.array; 11 12 enum AccessModifier : string 13 { 14 Public = "public", 15 Protected = "protected", 16 Private = "private", 17 Package = "package", 18 } 19 20 /** 21 * Generates a getter and setter for a field. 22 * 23 * Params: 24 * field = The field to generate the property for. 25 * setterAccess = The access modifier for the setter function. 26 * getterAccess = The access modifier for the getter funciton. 27 * name = The name of the property functions. Defaults to the field name minus the first character. Meant for fields that start with underscores. 28 */ 29 template Property( alias field, AccessModifier setterAccess = AccessModifier.Protected, AccessModifier getterAccess = AccessModifier.Public, string name = field.stringof[ 1..$ ] ) 30 { 31 enum Property = Getter!( field, getterAccess, name ) ~ Setter!( field, setterAccess, name ); 32 } 33 34 /** 35 * Generates a getter for a field. 36 * 37 * Params: 38 * field = The field to generate the property for. 39 * access = The access modifier for the getter function. 40 * name = The name of the property functions. Defaults to the field name minus the first character. Meant for fields that start with underscores. 41 */ 42 template Getter( alias field, AccessModifier access = AccessModifier.Public, string name = field.stringof[ 1..$ ] ) 43 { 44 enum Getter = q{ 45 final $access @property auto $name() @safe pure nothrow 46 { 47 return $field; 48 }} 49 .replaceMap( [ 50 "$field": field.stringof, "$name": name, 51 "$access": cast(string)access ] ); 52 } 53 54 /** 55 * Generates a getter for a field that returns a reference to it. 56 * 57 * Params: 58 * field = The field to generate the property for. 59 * access = The access modifier for the getter function. 60 * name = The name of the property functions. Defaults to the field name minus the first character. Meant for fields that start with underscores. 61 */ 62 template RefGetter( alias field, AccessModifier access = AccessModifier.Public, string name = field.stringof[ 1..$ ] ) 63 { 64 enum RefGetter = q{ 65 final $access @property auto ref $name() @safe pure nothrow 66 { 67 return $field; 68 }} 69 .replaceMap( [ 70 "$field": field.stringof, "$name": name, 71 "$access": cast(string)access ] ); 72 } 73 74 /** 75 * Generates a getter for a field that can be marked as dirty. Calls updateFunc if is dirty. 76 * 77 * Params: 78 * field = The field to generate the property for. 79 * updateFunc = The function to call when the function is dirty. 80 * access = The access modifier for the getter function. 81 * name = The name of the property functions. Defaults to the field name minus the first character. Meant for fields that start with underscores. 82 */ 83 template DirtyGetter( alias field, alias updateFunc, AccessModifier access = AccessModifier.Public, string name = field.stringof[ 1..$ ] ) 84 if( is( typeof(field) : IDirtyable ) ) 85 { 86 enum DirtyGetter = q{ 87 final $access @property auto $name() $attributes 88 { 89 if( $field.isDirty() ) 90 $updateFunc; 91 return $field; 92 }} 93 .replaceMap( [ 94 "$field": field.stringof, "$updateFunc": updateFunc.stringof, 95 "$name": name, "$access": cast(string)access, 96 "$attributes": functionTraitsString!updateFunc ] ); 97 } 98 99 /// ditto 100 template DirtyGetter( alias field, alias updateFunc, AccessModifier access = AccessModifier.Public, string name = field.stringof[ 1..$ ] ) 101 if( !is( typeof(field) : IDirtyable ) ) 102 { 103 enum DirtyGetter = q{ 104 private $type $dirtyFieldName; 105 final $access @property auto $name() $attributes 106 { 107 if( $field != $dirtyFieldName ) 108 $updateFunc; 109 return $field; 110 }} 111 .replaceMap( [ 112 "$field": field.stringof, "$updateFunc": updateFunc.stringof, 113 "$name": name, "$access": cast(string)access, 114 "$type": typeof(field).stringof, 115 "$dirtyFieldName": "_" ~ field.stringof ~ "Prev", 116 "$attributes": functionTraitsString!updateFunc ] ); 117 } 118 119 /** 120 * Like DirtyGetter, but instead of tracking if the field is dirty, it tracks if the this scope is dirty 121 * 122 * Params: 123 * field = The field to generate the property for. 124 * updateFunc = The function to call when the function is dirty. 125 * access = The access modifier for the getter function. 126 * name = The name of the property functions. Defaults to the field name minus the first character. Meant for fields that start with underscores. 127 */ 128 template ThisDirtyGetter( alias field, alias updateFunc, AccessModifier access = AccessModifier.Public, string name = field.stringof[ 1..$ ] ) 129 { 130 enum ThisDirtyGetter = q{ 131 final $access @property auto $name() $attributes 132 { 133 if( this.isDirty() ) 134 $updateFunc; 135 return $field; 136 }} 137 .replaceMap( [ 138 "$field": field.stringof, "$updateFunc": updateFunc.stringof, 139 "$name": name,"$access": cast(string)access, 140 "$attributes": functionTraitsString!updateFunc ] ); 141 } 142 143 /** 144 * Generates a setter for a field. 145 * 146 * Params: 147 * field = The field to generate the property for. 148 * access = The access modifier for the setter function. 149 * name = The name of the property functions. Defaults to the field name minus the first character. Meant for fields that start with underscores. 150 */ 151 template Setter( alias field, AccessModifier access = AccessModifier.Protected, string name = field.stringof[ 1..$ ] ) 152 { 153 enum Setter = ConditionalSetter!( field, q{true}, access, name ); 154 } 155 156 /** 157 * Generates a setter for a field, that only sets if a condition is met. 158 * 159 * Params: 160 * field = The field to generate the property for. 161 * condition = The condition to evaluate when assigning. 162 * access = The access modifier for the setter function. 163 * name = The name of the property functions. Defaults to the field name minus the first character. Meant for fields that start with underscores. 164 */ 165 template ConditionalSetter( alias field, string condition, AccessModifier access = AccessModifier.Protected, string name = field.stringof[ 1..$ ] ) 166 { 167 enum ConditionalSetter = q{ 168 final $access @property void $name( $type newVal ) @safe pure nothrow 169 { 170 if( $condition ) 171 $field = newVal; 172 }} 173 .replaceMap( [ 174 "$field": field.stringof, "$access": cast(string)access, 175 "$name": name, "$type": typeof(field).stringof, 176 "$condition": condition ] ); 177 } 178 179 /** 180 * Requires implementation of the isDirty property. 181 */ 182 interface IDirtyable 183 { 184 @property bool isDirty(); 185 } 186 187 private: 188 string functionTraitsString( alias func )() 189 { 190 string result = ""; 191 enum funcAttr = functionAttributes!func; 192 193 if( funcAttr & FunctionAttribute.trusted ) 194 result ~= " @trusted"; 195 if( funcAttr & FunctionAttribute.safe ) 196 result ~= " @safe"; 197 if( funcAttr & FunctionAttribute.pure_ ) 198 result ~= " pure"; 199 if( funcAttr & FunctionAttribute.nothrow_ ) 200 result ~= " nothrow"; 201 if( funcAttr & FunctionAttribute.ref_ ) 202 result ~= " ref"; 203 204 return result; 205 }