1 module dhtags.attrs.define;
2 
3 /**
4  * Define an html attribute with name and a list of types it accepts for its value.
5  */
6 template DefineAttrWithTypes(alias name, TVal...) {
7    import dhtags.attrs.attribute : AttrPair, BoolAttribute;
8    import dhtags.utils.html;
9    import std..string : format, replace, capitalize;
10    import std.traits : isSomeString;
11 
12    static if (isSomeString!(typeof(name))) {
13       // If a single name is provided it will be both the attribute symbol and name
14       enum attrSymbol = name.hyphenToCamelCase;
15       enum attrName = name;
16    } else { 
17       // Otherwise assume an array or tuple (symbol, name) was provided
18       enum attrSymbol = name[0].hyphenToCamelCase;
19       static if (name.length > 1) { enum attrName = name[1]; }
20       else { enum attrName = attrSymbol; }
21    }
22 
23    enum capitalizedAttrSymbol = capitalize(attrSymbol);
24 
25    /**
26     * Define an attribute that can be assigned to.
27     * e.g. 'struct IdAttr' for attribute 'id'
28     */
29    mixin(format(q{ 
30       enum %1$s = %2$sAttr();
31 
32       private struct %2$sAttr {
33          enum name = attrName;
34          alias TValues = TVal;
35          mixin DefineOpAssign;
36       }
37    }, attrSymbol, capitalizedAttrSymbol));
38 
39    mixin template DefineOpAssign() {
40       /** 
41        * Iterate through TVal tuple and define opAssign functions for all types for this attribute's value
42        */
43       private static string defineOpAssign(int i = 0)() {
44          static if (i < TVal.length) {
45             // Check if TVal[i] is actually a literal value and if so we'll define a member to store it
46             static if (__traits(compiles, typeof(TVal[i]))) {
47                return format(q{
48                   enum _allowedValue%1$d = asString!(TVal[%1$d]);
49                   static if (!hasType!(typeof(TVal[%1$d]), %1$d + 1)) {
50                      auto opAssign(typeof(TVal[%1$d]) value) { 
51                         return new AttrPair!(typeof(this), typeof(TVal[%1$d]))(name, value);
52                      }
53                   }
54                }, i) ~ defineOpAssign!(i + 1);
55 
56             // Otherwise TVal is a type so define an opAssign function to allow a value of this type to be assigned to the attribute
57             } else {
58                return format(q{
59                   static if (!hasType!(TVal[%1$d], %1$d + 1)) {
60                      auto opAssign(TVal[%1$d] value) {
61                         return new AttrPair!(typeof(this), TVal[%1$d])(name, value);
62                      }
63                   }
64 
65                   static if ((is(TVal[%1$d] : BoolAttribute))) {
66                      auto boolPair() {
67                         return new AttrPair!(typeof(this), string)(name, name);
68                      }
69                      alias boolPair this;
70                   }
71                }, i) ~ defineOpAssign!(i + 1);
72             }
73          } else { return ""; }
74       }
75 
76       mixin(defineOpAssign);
77    }
78 
79    /**
80     * Helper function to check if TVal starting at index i and onwards has the type T
81     */
82    bool hasType(T, int i)() {
83       static if (i < TVal.length) {
84          static if (__traits(compiles, typeof(TVal[i]))) {
85             return (is(typeof(TVal[i]) : T)) || hasType!(T, i + 1);
86          } else {
87             return (is(TVal[i] : T)) || hasType!(T, i + 1);
88          }
89       } else {
90          return false;
91       }
92    }
93 }
94 
95 /**
96  * Helper template to ensure all attributes can take values of type string.
97  */
98 template DefineAttr(alias name, TVal...) {
99    import dhtags.attrs.define : DefineAttrWithTypes;
100    mixin DefineAttrWithTypes!(name, string, TVal);
101 }
102 
103 /**
104  * Define a boolean html attribute with name and additional list of values.
105  * If present, the attribute's value will be rendered as an exact match of the attribute's name.
106  */
107 template DefineBooleanAttr(alias name, TVal...) {
108    import dhtags.attrs.attribute : BoolAttribute;
109    import dhtags.attrs.define : DefineAttr;
110    mixin DefineAttr!(name, BoolAttribute, TVal);
111 }