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 }