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 }