1 module libwasm.dom;
2 
3 import libwasm.types;
4 import memutils.ct;
5 import std.traits : hasMember, isAggregateType;
6 import std.traits : TemplateArgsOf, staticMap, isPointer, PointerTarget, getUDAs, EnumMembers, isInstanceOf, isBasicType, isIntegral;
7 import std.traits : getSymbolsByUDA, getUDAs;
8 import std.meta : Filter, AliasSeq, ApplyLeft, ApplyRight;
9 import libwasm.css;
10 import libwasm.node;
11 import libwasm.event;
12 import std.meta : staticIndexOf;
13 import libwasm.array;
14 import libwasm.rt.array;
15 
16 @safe:
17 nothrow:
18 
19 version (unittest)
20 {
21   import libwasm.sumtype;
22   import std.array : Appender;
23   import std.algorithm : remove, countUntil;
24   import std.array : insertInPlace;
25 
26   class UnittestDomNode
27   {
28     alias Property = SumType!(string, int, bool, double);
29     alias Attribute = SumType!(string, int);
30     NodeType type;
31     Handle handle;
32     UnittestDomNode parent;
33     Property[string] properties;
34     Attribute[string] attributes;
35     string[] classes;
36     Appender!(UnittestDomNode[]) children;
37     this(NodeType type, Handle handle) nothrow
38     {
39       this.type = type;
40       this.handle = handle;
41     }
42 
43     void setAttribute(T)(string name, T value) nothrow @trusted
44     {
45       attributes[name] = Attribute(value);
46     }
47 
48     Attribute getAttribute(string name) nothrow
49     {
50       return attributes[name];
51     }
52 
53     void setProperty(T)(string name, T value) nothrow @trusted
54     {
55       properties[name] = Property(value);
56     }
57 
58     Property getProperty(string name) nothrow
59     {
60       return properties[name];
61     }
62 
63     void toString(scope void delegate(const(char)[]) @safe sink) @trusted
64     {
65       import std.algorithm : each;
66       import std.format : formattedWrite, format;
67       import std.array : join;
68       import std.conv : to;
69 
70       if (type != NodeType.root)
71       {
72         sink.formattedWrite("<%s", type);
73         // write attributes and properties
74         if (classes.length > 0)
75         {
76           sink.formattedWrite(" class=\"%s\"", classes.join(" "));
77         }
78         foreach (kv; properties.byKeyValue())
79         {
80           sink.formattedWrite(" %s=%s", kv.key, libwasm.sumtype.match!((string s) => format(`"%s"`, s), (
81               i) => i.to!string)(kv.value));
82         }
83         foreach (kv; attributes.byKeyValue())
84         {
85           sink.formattedWrite(" %s=%s", kv.key, libwasm.sumtype.match!((string s) => format(`"%s"`, s), (
86               int i) => i.to!string)(kv.value));
87         }
88         sink(">");
89       }
90       children.data.each!(c => c.toString(sink));
91       // write children
92       if (type != NodeType.root)
93         sink.formattedWrite("</%s>", type);
94     }
95   }
96 
97   Appender!(UnittestDomNode[]) unittest_dom_nodes;
98   private auto getNode(Handle node)
99   {
100     assert(node > 0);
101     return unittest_dom_nodes.data[node - 1];
102   }
103 
104   private auto assumeNoException(Block)(lazy Block block)
105   {
106     try
107     {
108       block();
109     }
110     catch (Exception e)
111     {
112       assert(0, e.msg);
113     }
114   }
115 
116   extern (C)
117   {
118     Handle createElement(NodeType type)
119     {
120       uint idx = cast(uint) unittest_dom_nodes.data.length;
121       unittest_dom_nodes.put(new UnittestDomNode(type, idx + 1));
122       return idx + 1;
123     }
124 
125     void addClass(Handle node, string className)
126     {
127       node.getNode().classes ~= className;
128     }
129 
130     void setProperty(Handle node, string prop, string value)
131     {
132       node.getNode().setProperty(prop, value);
133     }
134 
135     void removeChild(Handle childPtr) @trusted
136     {
137       import std.algorithm : remove;
138       import std.algorithm : countUntil;
139 
140       auto child = childPtr.getNode();
141       auto childIdx = child.parent.children.data.countUntil!(c => c is child);
142       assert(childIdx >= 0);
143       child.parent.children.data.remove(childIdx);
144       child.parent.children.shrinkTo(child.parent.children.data.length - 1).assumeNoException();
145     }
146 
147     void unmount(Handle childPtr)
148     {
149       removeChild(childPtr);
150     }
151 
152     void appendChild(Handle parentPtr, Handle childPtr) @trusted
153     {
154       auto parent = parentPtr.getNode();
155       auto child = childPtr.getNode();
156       if (child.parent)
157       {
158         auto childIdx = child.parent.children.data.countUntil!(c => c is child);
159         if (childIdx > 0)
160         {
161           child.parent.children.data.remove(childIdx);
162           child.parent.children.shrinkTo(child.parent.children.data.length - 1).assumeNoException();
163         }
164       }
165       child.parent = parent;
166       parent.children.put(child);
167     }
168 
169     void insertBefore(Handle parentPtr, Handle childPtr, Handle siblingPtr) @trusted
170     {
171       auto parent = parentPtr.getNode();
172       auto child = childPtr.getNode();
173       auto sibling = siblingPtr.getNode();
174       if (child.parent)
175       {
176         auto childIdx = child.parent.children.data.countUntil!(c => c is child);
177         if (childIdx > 0)
178         {
179           child.parent.children.data.remove(childIdx);
180           child.parent.children.shrinkTo(child.parent.children.data.length - 1).assumeNoException();
181         }
182       }
183       child.parent = parent;
184       auto siblingIdx = parent.children.data.countUntil!(c => c is sibling);
185       assert(siblingIdx >= 0);
186       parent.children.put(child);
187       auto arr = parent.children.data;
188 
189       foreach (i; 0 .. arr.length - siblingIdx)
190       {
191         if (arr.length - i > 1)
192           arr[arr.length - 1 - i] = arr[arr.length - 2 - i];
193       }
194       arr[siblingIdx] = child;
195     }
196 
197     void setAttribute(Handle node, string attr, string value)
198     {
199       node.getNode().setAttribute(attr, value);
200     }
201 
202     void setAttributeInt(Handle node, string attr, int value)
203     {
204       node.getNode().setAttribute(attr, value);
205     }
206 
207     void setPropertyBool(Handle node, string prop, bool value)
208     {
209       node.getNode().setProperty(prop, value);
210     }
211 
212     void setPropertyInt(Handle node, string prop, int value)
213     {
214       node.getNode().setProperty(prop, value);
215     }
216 
217     void setPropertyDouble(Handle node, string prop, double value)
218     {
219       node.getNode().setProperty(prop, value);
220     }
221 
222     void innerText(Handle nodePtr, string text)
223     {
224     }
225 
226     void removeClass(Handle node, string className)
227     {
228       import std.algorithm : remove;
229 
230       auto n = node.getNode();
231       n.classes = n.classes.remove!(i => i == className);
232     }
233 
234     void changeClass(Handle node, string className, bool on)
235     {
236       import std.algorithm : any;
237 
238       auto n = node.getNode();
239       if (on && !n.classes.any!(c => c == className))
240         n.classes ~= className;
241       else if (!on)
242         removeClass(node, className);
243     }
244 
245     Optional!string getProperty(Handle node, string name)
246     {
247       auto str = node.getNode().getProperty(name).trustedGet!string;
248       return some(str);
249     }
250 
251     Optional!int getPropertyInt(Handle node, string prop)
252     {
253       return some(node.getNode().getProperty(prop).trustedGet!int);
254     }
255 
256     Optional!bool getPropertyBool(Handle node, string prop)
257     {
258       return some(node.getNode().getProperty(prop).trustedGet!bool);
259     }
260 
261     Optional!double getPropertyDouble(Handle node, string prop)
262     {
263       return some(node.getNode().getProperty(prop).trustedGet!double);
264     }
265 
266     void setSelectionRange(Handle node, uint start, uint end)
267     {
268     }
269 
270     void addCss(string css)
271     {
272     }
273   }
274 }
275 else
276 {
277   private extern (C)
278   {
279     Handle createElement(NodeType type);
280     void addClass(Handle node, string className);
281     void setProperty(Handle node, string prop, string value);
282     void removeChild(Handle childPtr);
283     void unmount(Handle childPtr);
284     void appendChild(Handle parentPtr, Handle childPtr);
285     void insertBefore(Handle parentPtr, Handle childPtr, Handle sibling);
286     void setAttribute(Handle nodePtr, string attr, string value);
287     void setAttributeInt(Handle nodePtr, string attr, int value);
288     void setPropertyBool(Handle nodePtr, string attr, bool value);
289     void setPropertyInt(Handle nodePtr, string attr, int value);
290     void setPropertyDouble(Handle nodePtr, string attr, double value);
291     void innerText(Handle nodePtr, string text);
292     void removeClass(Handle node, string className);
293     void changeClass(Handle node, string className, bool on);
294   }
295 
296   extern (C)
297   {
298     string getProperty(Handle node, string prop);
299     int getPropertyInt(Handle node, string prop);
300     bool getPropertyBool(Handle node, string prop);
301     void getPropertyDouble(Handle nodePtr, string prop);
302     void setSelectionRange(Handle node, uint start, uint end);
303     void addCss(string css);
304   }
305 }
306 
307 import libwasm.bindings.Document : Document;
308 import libwasm.bindings.Window : Window;
309 
310 @trusted ref auto undefined()
311 {
312   static __gshared u = Any(JsHandle(0));
313   return u;
314 }
315 
316 @trusted ref auto document()
317 {
318   static __gshared d = Document(JsHandle(1));
319   return d;
320 }
321 
322 @trusted ref auto window()
323 {
324   static __gshared w = Window(JsHandle(2));
325   return w;
326 }
327 
328 void unmount(T)(auto ref T t) if (is(T : SumType!(Types), Types...))
329 {
330   t.match!(unmount);
331 }
332 
333 void unmount(T)(auto ref T t) if (hasMember!(T, "node"))
334 {
335   unmount(t.node.node);
336   static if (hasMember!(T, "node"))
337     t.node.mounted = false;
338   t.propagateOnUnmount();
339 }
340 
341 auto removeChild(T)(T* t) if (is(T : SumType!(Types), Types...))
342 {
343   unmount(*t);
344 }
345 
346 auto removeChild(T)(auto ref T t) if (hasMember!(T, "node"))
347 {
348   removeChild(t.node.node);
349   static if (hasMember!(T, "node"))
350     t.node.mounted = false;
351   t.propagateOnUnmount();
352 }
353 
354 auto focus(T)(auto ref T t) if (hasMember!(T, "node"))
355 {
356   t.node.node.focus();
357 }
358 
359 auto renderBefore(T, Ts...)(Handle parent, return auto ref T t, Handle sibling, return auto ref Ts ts)
360 {
361   static if (is(T : SumType!Types, Types...))
362   {
363     t.match!((scope ref i) => renderBefore(parent, i, sibling, ts));
364   }
365   else
366   {
367     if (parent == invalidHandle)
368       return;
369     renderIntoNode(parent, t, ts);
370     static if (hasMember!(T, "node"))
371     {
372       parent.insertBefore(t.node.node, sibling);
373       t.node.mounted = true;
374     }
375     t.propagateOnMount();
376   }
377 }
378 
379 auto render(T, Ts...)(Handle parent, return auto ref T t, return auto ref Ts ts) @trusted
380 {
381   static if (is(T : SumType!Types, Types...))
382   {
383     t.match!((scope ref i) => render(parent, i, ts));
384   }
385   else
386   {
387     if (parent == invalidHandle)
388       return;
389     renderIntoNode(parent, t, ts);
390     static if (hasMember!(T, "node"))
391     {
392       if (!t.getNamedNode().mounted)
393       {
394         parent.appendChild(t.getNamedNode.node);
395         t.getNamedNode().mounted = true;
396       }
397     }
398     t.propagateOnMount();
399   }
400 }
401 
402 import std.traits : isFunction;
403 
404 auto propagateOnMount(T)(auto ref T t)
405 {
406   static foreach (c; getChildren!T)
407     __traits(getMember, t, c).propagateOnMount();
408   static if (hasMember!(T, "onMount") && isFunction!(T.onMount))
409     t.onMount();
410 }
411 
412 auto propagateOnUnmount(T)(auto ref T t)
413 {
414   static foreach (c; getChildren!T)
415     __traits(getMember, t, c).propagateOnMount();
416   static if (hasMember!(T, "onUnmount") && isFunction!(T.onUnmount))
417     t.onUnmount();
418 }
419 
420 auto remount(string field, Parent)(auto ref Parent parent)
421 {
422   import std.traits : hasUDA;
423   import std.meta : AliasSeq;
424 
425   alias fields = AliasSeq!(__traits(allMembers, Parent));
426   alias idx = staticIndexOf!(field, fields);
427   static if (fields.length > idx + 1)
428   {
429     static foreach (child; fields[idx + 1 .. $])
430     {
431       static if (hasUDA!(__traits(getMember, Parent, child), libwasm.types.child))
432       {
433         if (__traits(getMember, parent, child).node.mounted)
434           return renderBefore(parent.node.node, __traits(getMember, parent, field), __traits(getMember, parent, child)
435               .node.node, parent);
436       }
437     }
438   }
439   return render(parent.node.node, __traits(getMember, parent, field), parent);
440 }
441 
442 template isValue(alias t)
443 {
444   enum isValue = __traits(compiles, { enum p = t; });
445 }
446 
447 template createParameterTuple(Params...)
448 {
449   auto createParameterTuple(Ts...)(return auto ref Ts ts)
450   {
451     import std.traits : TemplateArgsOf, staticMap;
452     import libwasm.spa : Param;
453 
454     template extractName(Arg)
455     {
456       enum extractName = Arg.Name;
457     }
458 
459     static auto extractField(alias sym)(return auto ref Ts ts) @trusted
460     {
461       enum name = sym.stringof;
462       enum literal = isValue!(sym);
463       static if (isValue!(sym))
464       {
465         static if (isBasicType!(typeof(sym)))
466         {
467           typeof(sym) val = sym;
468           return val;
469         }
470         else
471         {
472           __gshared static auto val = sym;
473           return &val;
474         }
475       }
476       else
477       {
478         alias ContainingType = AliasSeq!(__traits(parent, sym))[0];
479         template isContainingType(ContainingType, T)
480         {
481           enum isContainingType = is(ContainingType == T);
482         }
483 
484         enum index = indexOfPred!(ApplyLeft!(isContainingType, ContainingType), AliasSeq!Ts);
485         return &__traits(getMember, ts[index], name);
486       }
487     }
488 
489     static auto extractFields(Args...)(return auto ref Ts ts) @safe
490         if (Args.length > 0)
491     {
492       static if (Args.length > 1)
493         return tuple(extractField!(TemplateArgsOf!(Args[0])[1])(ts), extractFields!(Args[1 .. $])(
494           ts).expand);
495       else
496         return tuple(extractField!(TemplateArgsOf!(Args[0])[1])(ts));
497     }
498 
499     alias ParamsTuple = staticMap!(TemplateArgsOf, Params);
500     alias Names = staticMap!(extractName, ParamsTuple);
501     auto Fields = cast() extractFields!(ParamsTuple)(ts);
502     return tuple!(Names)(Fields.expand);
503   }
504 }
505 
506 @trusted
507 void setParamFromParent(string injected_name, string local_name, T, Ts...)(
508   return auto ref T t, return auto ref Ts ts)
509 {
510   import std.traits : PointerTarget, isPointer;
511   import std.meta : AliasSeq;
512 
513   alias TargetType = typeof(getMember!(T, local_name));
514   static if (isPointer!(TargetType))
515     alias FieldType = PointerTarget!(TargetType);
516   else
517     alias FieldType = TargetType;
518   template matchesField(Parent)
519   {
520     static if (!hasMember!(Parent, injected_name))
521       enum matchesField = false;
522     else
523     {
524       alias ItemTypeParent = AliasSeq!(__traits(parent, getMember!(Parent, injected_name)))[0];
525       static if (is(T == ItemTypeParent))
526         enum matchesField = false;
527       else
528       {
529         alias ItemType = typeof(getMember!(Parent, injected_name));
530         enum matchesField = (is(ItemType == FieldType) || is(ItemType == FieldType*));
531       }
532     }
533   }
534 
535   enum index = indexOfPred!(matchesField, AliasSeq!Ts);
536   static if (index >= ts.length)
537   {
538     return;
539   }
540   else
541   {
542     alias SourceType = typeof(__traits(getMember, Ts[index], injected_name));
543     static if (isPointer!TargetType)
544     {
545       static if (isPointer!SourceType)
546         __traits(getMember, t, local_name) = __traits(getMember, ts[index], injected_name);
547       else
548       {
549         static if (is(Ts[index] : Tuple!Fields, Fields...))
550         {
551           static assert(!isBasicType!SourceType, "Cannot assign literal in param uda to a pointer, use member field or avoid use of pointer.");
552         }
553         __traits(getMember, t, local_name) = &__traits(getMember, ts[index], injected_name);
554       }
555     }
556     else static if (isPointer!SourceType)
557       __traits(getMember, t, local_name) = *__traits(getMember, ts[index], injected_name);
558     else
559     {
560       __traits(getMember, t, local_name) = __traits(getMember, ts[index], injected_name);
561     }
562   }
563 }
564 
565 void setChildFromParent(string injected_name, string local_name, T, Ts...)(
566   return auto ref T t, return auto ref Ts ts)
567 {
568   template containsChild(Parent)
569   {
570     enum containsChild = hasMember!(Parent, injected_name);
571   }
572 
573   enum index = indexOfPred!(containsChild, AliasSeq!Ts);
574   static if (index != -1)
575   {
576     alias ChildMemberType = typeof(__traits(getMember, T, local_name));
577     alias Parent = Ts[index];
578     alias ParentMemberType = typeof(__traits(getMember, Parent, injected_name));
579     static assert(__traits(hasMember, ParentMemberType, "node"), __traits(identifier, ParentMemberType) ~ " doesn't have a member 'node'.");
580     alias ParentMemberNodeType = typeof(ParentMemberType.node);
581     alias ChildMemberNodeType = typeof(ChildMemberType.node);
582     static assert(is(ParentMemberNodeType : NamedNode!type, string type), __traits(identifier, ParentMemberType) ~ "'s member 'node' needs to be of type NamedNode.");
583     // NOTE: for now we assert that the ChildMemberNodeType is an HTMLElement, it would be nice to
584     // allow the component to restrict the nodetype of child elements, in which case
585     // we need to check if ChildMemberNodeType is a supertype or the same as ParentMemberNodeType
586     // this way we could e.g. enforce a child of a Table component to be <tr>
587     import libwasm.bindings.HTMLElement : HTMLElement;
588 
589     static assert(is(ChildMemberNodeType : HTMLElement));
590     auto ptr = &__traits(getMember, ts[index], injected_name).node;
591     __traits(getMember, t, local_name) = cast(ChildMemberType) ptr;
592   }
593 }
594 
595 void verifyChildParams(Super, string parentField, Params...)()
596 {
597   import std.traits : hasUDA;
598 
599   alias ParamsTuple = staticMap!(TemplateArgsOf, Params);
600   static foreach (Param; AliasSeq!ParamsTuple)
601   {
602     {
603       enum childName = __traits(identifier, Param.Field);
604       alias ChildMemberType = typeof(Param.Field);
605       static if (!isBasicType!(ChildMemberType) && !isValue!(Param.Field) && hasMember!(Super, childName))
606       {
607         enum childOffset = __traits(getMember, Super, childName).offsetof;
608         enum parentOffset = __traits(getMember, Super, parentField).offsetof;
609 
610         // To make sure we don't render injected fields
611         static assert(!hasUDA!(__traits(getMember, Super, childName), child), "Injected member '" ~ childName ~ "' cannot have @child attribute in struct " ~ Super
612             .stringof);
613 
614         // To make sure we don't inject an injected field
615         // Todo: Omit @inject fields from the tuple
616         static assert(childOffset > parentOffset, "Injected member '" ~ childName ~ "' needs to be declared after '" ~ parentField ~ "' in struct " ~ Super
617             .stringof);
618       }
619     }
620   }
621 }
622 
623 // todo: loadFromHTML
624 // parse DOM, iterate structs to find element placement, set values, recur for each child
625 // if the child list is equivalent to the parsed dom children, populate each child and attributes
626 // otherwise, build an array with the missing child nodes, and match the children using the ID while verifying the ordering
627 // if the struct is missing attributes, build an array with the missing attributes
628 // if all attributes are missing and there are no children in the struct, use innerHTML
629 // call remount after this is done
630 // todo: create the logic in render()
631 // iterate the children and attributes arrays to load the ones that were missing
632 // use a placement array to set the position relative to existing children nodes
633 
634 // Populates the injected fields
635 auto compile(T, Ts...)(return auto ref T t, return auto ref Ts ts) @trusted
636 {
637   import std.meta : AliasSeq;
638   import std.traits : hasUDA;
639 
640   static foreach (sym; T.tupleof)
641   {
642     {
643       static if (isPointer!(typeof(sym)))
644         alias ChildType = PointerTarget!(typeof(sym));
645       else
646         alias ChildType = typeof(sym);
647       enum i = sym.stringof; // TODO: can this be fieldName = __traits(identifier, sym) instead of i = sym.stringof ??
648       enum isImmutable = is(typeof(sym) : immutable(T), T);
649       enum isPublic = __traits(getProtection, sym) == "public";
650 
651       static if (!isImmutable)
652       {
653 
654         static if (is(ChildType : NamedNode!name, string name))
655         {
656           static if (i != "node")
657           { // reference to a parent struct
658             setChildFromParent!(__traits(identifier, sym), __traits(identifier, sym))(t, ts);
659 
660             alias udas = getUDAs!(sym, inject);
661             static foreach (uda; udas)
662             {
663               static if (is(uda == inject!ref_name, string ref_name))
664               {
665                 setChildFromParent!(ref_name, __traits(identifier, sym))(t, ts);
666                 break;
667               }
668             }
669           }
670         }
671         else
672         {
673           static if (isPublic)
674           { // reference to a parent member   
675 
676             setParamFromParent!(i, i)(t, ts);
677             alias udas = getUDAs!(sym, inject);
678             static foreach (uda; udas)
679             {
680               static if (is(uda == inject!ref_name, string ref_name))
681               {
682                 setParamFromParent!(ref_name, i)(t, ts);
683                 break;
684               }
685             }
686           }
687           static if (isPublic && !is(sym) && isAggregateType!T)
688           {
689 
690             static if (is(typeof(sym) : DynamicArray!(Item), Item))
691             {
692               // items in appenders need to be set via render functions
693             }
694             else
695             {
696               static if (!isCallable!(typeof(sym)))
697               { //} && !isPointer!(typeof(sym))) {
698 
699                 import libwasm.spa;
700 
701                 alias Params = getUDAs!(sym, Parameters);
702                 static if (Params.length > 0)
703                 {
704                   verifyChildParams!(T, i, Params);
705                   auto params = createParameterTuple!(Params)(AliasSeq!(t, ts));
706                 }
707                 else
708                   alias params = AliasSeq!();
709                 static if (isAggregateType!(ChildType))
710                 { // recurse through the child members
711 
712                   static if (isPointer!(typeof(sym)))
713                     compile(*__traits(getMember, t, i), AliasSeq!(params, t, ts));
714                   else
715                     compile(__traits(getMember, t, i), AliasSeq!(params, t, ts));
716                 }
717               }
718             }
719           }
720         }
721       }
722     }
723   }
724 
725   // adds too much complexity? onMount / onUnmount would be better?
726 
727   static if (hasMember!(T, "construct"))
728     t.construct();
729 
730 }
731 
732 // todo: addRouterHandlers
733 // todo: removeRouterHandlers
734 
735 auto isChildVisible(string child, Parent)(return auto ref Parent parent)
736 {
737   alias visiblePreds = getSymbolsByUDA!(Parent, visible);
738   static foreach (sym; visiblePreds)
739   {
740     {
741       alias vs = getUDAs!(sym, visible);
742       // TODO: static assert sym is callable
743       static foreach (v; vs)
744       {
745         {
746           static if (is(v == visible!name, string name) && child == name)
747           {
748             static if (is(typeof(sym) == bool))
749             {
750               if (!__traits(getMember, parent, __traits(identifier, sym)))
751                 return false;
752             }
753             else
754             {
755               auto result = callMember!(__traits(identifier, sym))(parent);
756               if (result == false)
757                 return false;
758             }
759           }
760         }
761       }
762     }
763   }
764   return true;
765 }
766 
767 auto callMember(string fun, T)(return auto ref T t)
768 {
769   import memutils.ct : ParameterIdentifierTuple;
770   import std.meta : staticMap, AliasSeq;
771 
772   alias params = ParameterIdentifierTuple!(__traits(getMember, t, fun));
773   static if (params.length == 0)
774   {
775     return __traits(getMember, t, fun)();
776   }
777   else static if (params.length == 1)
778   {
779     return __traits(getMember, t, fun)(__traits(getMember, t, params[0]));
780   }
781   else static if (params.length == 2)
782   {
783     return __traits(getMember, t, fun)(__traits(getMember, t, params[0]), __traits(getMember, t, params[1]));
784   }
785   else
786   {
787     static assert(false, "Not implemented");
788   }
789 }
790 
791 auto renderIntoNode(T, Ts...)(Handle parent, return auto ref T t, return auto ref Ts ts)
792     if (isPointer!T)
793 {
794   return renderIntoNode(parent, *t, ts);
795 }
796 
797 ref auto getNamedNode(T)(return auto ref T t) if (isPointer!T)
798 {
799   return (*t).getNamedNode();
800 }
801 
802 ref auto getNamedNode(T)(return auto ref T t) if (!isPointer!T)
803 {
804   import libwasm.node : NamedNode;
805 
806   static if (is(T : NamedNode!name, string name))
807   {
808     return t;
809   }
810   else static if (hasMember!(T, "node"))
811   {
812     return t.node;
813   }
814   else
815   {
816     alias children = getSymbolsByUDA!(T, child);
817     static assert(children.length == 1);
818     return __traits(getMember, t, __traits(identifier, children[0])).getNamedNode();
819   }
820 }
821 
822 template createNestedChildRenderFuncs(string memberName)
823 {
824   auto createNestedChildRenderFuncs(T)(return auto ref T t)
825   {
826     import std.traits : hasUDA;
827     import std.typecons : tuple;
828 
829     template isNestedChild(alias member)
830     {
831       alias MemberType = typeof(member);
832       static if (!__traits(hasMember, MemberType, "node"))
833         enum isNestedChild = false;
834       else
835         enum isNestedChild = is(typeof(MemberType.node) : NamedNode!type, string type);
836     }
837 
838     template extractName(alias param)
839     {
840       alias extractName = param.Name;
841     }
842 
843     template extractField(alias param)
844     {
845       alias extractField = param.Field;
846     }
847 
848     template isFieldNestedChild(alias param)
849     {
850       enum isFieldNestedChild = isNestedChild!(extractField!(param));
851     }
852 
853     auto createRenderFunc(alias param)() @trusted
854     {
855       enum nestedChildName = __traits(identifier, extractField!(param));
856       return cast(NestedChildRenderFunc)(Handle parent) @safe {
857         auto nestedChildRenderFuncs = .createNestedChildRenderFuncs!(nestedChildName)(t);
858         render(parent, __traits(getMember, t, nestedChildName), nestedChildRenderFuncs);
859       };
860     }
861 
862     alias ParamsTuple = getAnnotatedParameters!(__traits(getMember, T, memberName));
863     alias ParamFieldsTuple = staticMap!(extractField, ParamsTuple);
864     alias nestedChildParams = Filter!(isFieldNestedChild, ParamsTuple);
865     alias Names = staticMap!(extractName, nestedChildParams);
866     static if (Names.length == 0)
867       return tuple();
868     else
869       return tuple!(Names)(staticMap!(createRenderFunc, nestedChildParams));
870   }
871 }
872 
873 alias NestedChildRenderFunc = void delegate(uint) nothrow @safe;
874 
875 template renderNestedChild(string field)
876 {
877   template hasRenderFunc(alias T)
878   {
879     static if (!hasMember!(T, field))
880       enum hasRenderFunc = false;
881     else
882     {
883       alias fieldType = typeof(__traits(getMember, T, field));
884       enum hasRenderFunc = is(fieldType : NestedChildRenderFunc);
885     }
886   }
887 
888   auto renderNestedChild(T, Ts...)(Handle parent, return auto ref T t, return auto ref Ts ts)
889   {
890     enum tupleIndex = indexOfPred!(hasRenderFunc, Ts);
891     static if (tupleIndex != -1)
892     {
893       __traits(getMember, ts[tupleIndex], field)(parent);
894     }
895   }
896 }
897 // NOTE: only trusted because of the one __gshared thing
898 @trusted auto renderIntoNode(T, Ts...)(Handle parent, return auto ref T t, return auto ref Ts ts)
899     if (!isPointer!T)
900 {
901   import std.traits : hasUDA, getUDAs;
902   import std.meta : AliasSeq;
903   import std.meta : staticMap;
904   import std.traits : isCallable, getSymbolsByUDA, isPointer;
905 
906   enum hasNode = hasMember!(T, "node");
907   static if (hasNode)
908   {
909     bool shouldRender = t.getNamedNode().node == invalidHandle;
910   }
911   else
912     bool shouldRender = true;
913   if (shouldRender)
914   {
915     auto node = createNode(parent, t);
916     static if (hasMember!(T, "node"))
917     {
918       t.getNamedNode().node.handle.handle = node;
919     }
920     alias StyleSet = getStyleSet!T;
921     static foreach (sym; T.tupleof)
922     {
923       {
924         enum i = sym.stringof;
925         alias name = getSymbolCustomName!(sym, domName!i);
926         static if (!is(sym))
927         {
928           alias styles = getStyles!(sym);
929           static if (hasUDA!(sym, child))
930           {
931             import libwasm.spa;
932 
933             alias params = AliasSeq!();
934             if (isChildVisible!(i)(t))
935             {
936               static if (is(typeof(sym) : DynamicArray!(Item*), Item))
937               {
938                 foreach (ref item; __traits(getMember, t, i))
939                 {
940                   node.render(*item, AliasSeq!(params));
941                   static if (is(typeof(t) == HTMLArray!Item))
942                     t.assignEventListeners(*item);
943                 }
944               }
945               else static if (is(typeof(sym) : NamedNode!(name)*, string name))
946               {
947                 renderNestedChild!(i)(node, t, ts);
948               }
949               else
950               {
951                 auto nestedChildRenderFuncs = createNestedChildRenderFuncs!(i)(t);
952                 node.render(__traits(getMember, t, i), AliasSeq!(params, nestedChildRenderFuncs));
953               }
954             }
955           }
956           else static if (hasUDA!(sym, prop))
957           {
958             node.setPropertyTyped!name(__traits(getMember, t, i));
959           }
960           else static if (hasUDA!(sym, attr))
961           {
962             node.setAttributeTyped!name(__traits(getMember, t, i));
963           }
964 
965           alias extendedStyles = getStyleSets!(sym);
966           static foreach (style; extendedStyles)
967           {
968             static assert(hasMember!(typeof(sym), "node"), "styleset on field is currently only possible when said field has a Node mixin");
969 
970             __traits(getMember, t, i).node.setAttribute(GenerateExtendedStyleSetName!style, "");
971           }
972           static if (i == "node")
973           {
974             node.applyStyles!(T, styles);
975           }
976           else static if (styles.length > 0)
977           {
978             static if (isCallable!(sym))
979             {
980               auto result = callMember!(i)(t);
981               if (result == true)
982               {
983                 t.getNamedNode.applyStyles!(T, styles);
984               }
985             }
986             else static if (is(typeof(sym) == bool))
987             {
988               if (__traits(getMember, t, i) == true)
989                 t.getNamedNode.applyStyles!(T, styles);
990             }
991             else static if (hasUDA!(sym, child))
992             {
993               getNamedNode(__traits(getMember, t, i)).applyStyles!(T, styles);
994             }
995           }
996         }
997       }
998     }
999     static foreach (i; __traits(allMembers, T))
1000     {
1001       {
1002         static if (!is(__traits(getMember, T, i)) && isCallable!(__traits(getMember, T, i)))
1003         {
1004           alias sym = AliasSeq!(__traits(getMember, T, i))[0];
1005 
1006           alias name = getSymbolCustomName!(sym, domName!i);
1007           static if (hasUDA!(sym, child))
1008             static assert(false, "we don't support @child functions");
1009           else static if (hasUDA!(sym, prop))
1010           {
1011             auto result = callMember!(i)(t);
1012             t.getNamedNode.setPropertyTyped!name(result);
1013           }
1014           else static if (hasUDA!(sym, callback))
1015           {
1016             t.getNamedNode.addEventListenerTyped!i(t);
1017           }
1018           else static if (hasUDA!(sym, attr))
1019           {
1020             auto result = callMember!(i)(t);
1021             t.getNamedNode.setAttributeTyped!name(result);
1022           }
1023           else static if (hasUDA!(sym, style))
1024           {
1025             auto result = callMember!(i)(t);
1026             static foreach (style; getStyles!(sym))
1027             {
1028               __gshared static string className = GetCssClassName!(T, style);
1029               t.getNamedNode.changeClass(className, result);
1030             }
1031           }
1032           else static if (hasUDA!(sym, connect))
1033           {
1034             alias connects = getUDAs!(sym, connect);
1035             static foreach (c; connects)
1036             {
1037               auto del = &__traits(getMember, t, i);
1038               static if (is(c : connect!(a, b), alias a, alias b))
1039               {
1040                 mixin("t." ~ a ~ "." ~ replace!(b, '.', '_') ~ ".add(del);");
1041               }
1042               else static if (is(c : connect!field, alias field))
1043               {
1044                 static assert(__traits(compiles, mixin("t." ~ field)), "Cannot find property " ~ field ~ " on " ~ T
1045                     .stringof ~ " in @connect");
1046                 mixin("t." ~ field ~ ".add(del);");
1047               }
1048               else static if (is(c : connect!field, string field))
1049               {
1050                 mixin("t." ~ field ~ ".add(del);");
1051               }
1052             }
1053           }
1054         }
1055       }
1056     }
1057 
1058     alias enumsWithApplyStyles = getSymbolsByUDA!(T, ApplyStyle);
1059     static foreach (member; enumsWithApplyStyles)
1060     {
1061       {
1062         alias ApplyStyles = getUDAs!(member, ApplyStyle);
1063         static foreach (s; ApplyStyles)
1064         {
1065           static if (is(s == ApplyStyle!Target, alias Target))
1066           {
1067             static if (isFunction!member)
1068               alias EnumType = ReturnType!member;
1069             else
1070               alias EnumType = typeof(member);
1071             alias GetUDAs = ApplyRight!(ApplyLeft!(getEnumUDAs, EnumType), style);
1072             alias table = staticMap!(GetUDAs, __traits(allMembers, EnumType));
1073             alias GetCssClass = ApplyLeft!(GetCssClassName, T);
1074             alias classes = staticMap!(GetCssClass, staticMap!(extractStyleStruct, table));
1075             enum string[classes.length] styleTable = [classes];
1076             size_t idx = __traits(getMember, t, __traits(identifier, member));
1077             static if (__traits(identifier, Target) == "node")
1078             {
1079               node.addClass(styleTable[idx]);
1080             }
1081             else static if (hasUDA!(Target, child))
1082             {
1083               __traits(getMember, t, __traits(identifier, Target)).getNamedNode.addClass(
1084                 styleTable[idx]);
1085             }
1086             else
1087               static assert(false, "Can only have ApplyStyle point to node or to a child component");
1088           }
1089         }
1090       }
1091     }
1092   }
1093 }
1094 
1095 template getSymbolCustomName(alias symbol, string defaultName)
1096 {
1097   alias names = getStringUDAs!(symbol);
1098   static if (names.length > 0)
1099     enum getSymbolCustomName = names[0];
1100   else
1101     enum getSymbolCustomName = defaultName;
1102 }
1103 
1104 template getEnumUDAs(EnumType, string field, alias UDA)
1105 {
1106   alias udas = AliasSeq!(__traits(getAttributes, __traits(getMember, EnumType, field)));
1107   alias getEnumUDAs = Filter!(isDesiredUDA!UDA, udas);
1108 }
1109 
1110 private template isDesiredUDA(alias attribute)
1111 {
1112   template isDesiredUDA(alias toCheck)
1113   {
1114     static if (is(typeof(attribute)) && !__traits(isTemplate, attribute))
1115     {
1116       static if (__traits(compiles, toCheck == attribute))
1117         enum isDesiredUDA = toCheck == attribute;
1118       else
1119         enum isDesiredUDA = false;
1120     }
1121     else static if (is(typeof(toCheck)))
1122     {
1123       static if (__traits(isTemplate, attribute))
1124         enum isDesiredUDA = isInstanceOf!(attribute, typeof(toCheck));
1125       else
1126         enum isDesiredUDA = is(typeof(toCheck) == attribute);
1127     }
1128     else static if (__traits(isTemplate, attribute))
1129       enum isDesiredUDA = isInstanceOf!(attribute, toCheck);
1130     else
1131       enum isDesiredUDA = is(toCheck == attribute);
1132   }
1133 }
1134 
1135 template hasMemberWithInjectOf(string field, alias c)
1136 {
1137   enum bool hasMemberWithInjectOf = () {
1138     alias ChildType = typeof(c);
1139     alias getSymbol = ApplyLeft!(getMember, c);
1140     alias childrenNames = getChildren!ChildType;
1141     alias children = staticMap!(getSymbol, childrenNames);
1142     static foreach (c; children)
1143     {
1144       alias udas = getUDAs!(c, inject);
1145       static foreach (uda; udas)
1146       {
1147         static if (is(uda == inject!ref_name, string ref_name))
1148         {
1149           static if (ref_name == field)
1150           {
1151             return true;
1152           }
1153         }
1154       }
1155     }
1156     return false;
1157   }();
1158 }
1159 
1160 template among(alias field, T...)
1161 {
1162   static if (T.length == 0)
1163     enum among = false;
1164   else static if (T.length == 1)
1165     enum among = field.stringof == T[0];
1166   else
1167     enum among = among!(field, T[0 .. $ / 2]) || among!(field, T[$ / 2 .. $]);
1168 }
1169 
1170 template getAnnotatedParameters(alias symbol)
1171 {
1172   import libwasm.spa;
1173 
1174   alias Params = getUDAs!(symbol, Parameters);
1175   alias getAnnotatedParameters = staticMap!(TemplateArgsOf, Params);
1176 }
1177 
1178 // todo: Test if this works for @inject
1179 template updateChildren(alias member)
1180 {
1181   enum field = __traits(identifier, member);
1182   alias Source = AliasSeq!(__traits(parent, member))[0];
1183   template isParamField(Param)
1184   {
1185     enum isParamField = TemplateArgsOf!(Param)[1].stringof == field;
1186   }
1187 
1188   static void updateChildren(Parent)(return auto ref Parent parent)
1189   {
1190     // we are updating field in parent
1191     // all children that have a pointer with the exact same name
1192     // should get an update
1193     // all children that have a params annotation that refers to the field
1194     // should get an update
1195     import std.traits : getSymbolsByUDA;
1196     import std.meta : ApplyLeft, staticMap;
1197 
1198     static if (isPointer!(Parent))
1199       alias ParentType = PointerTarget!(Parent);
1200     else
1201       alias ParentType = Parent;
1202 
1203     // look for functions using the field as a parameter
1204     static foreach (t; ParentType.tupleof)
1205     {
1206       static foreach (param; getAnnotatedParameters!(t))
1207       {
1208         static if (!isValue!(TemplateArgsOf!(param)[1]))
1209         {
1210           alias target = TemplateArgsOf!(param)[1];
1211           static if (__traits(isSame, target, member))
1212           {
1213             __traits(getMember, parent, __traits(identifier, t)).update!(__traits(getMember, __traits(getMember, parent, __traits(
1214                 identifier, t)), param.Name));
1215           }
1216         }
1217       }
1218     }
1219 
1220     // propagate through members with a @child UDA
1221     alias getSymbol = ApplyLeft!(getMember, parent);
1222     alias childrenNames = getChildren!Parent;
1223     alias children = staticMap!(getSymbol, childrenNames);
1224     static foreach (c; children)
1225     {
1226       {
1227         alias ChildType = typeof(c);
1228 
1229         static if (hasMember!(ChildType, field))
1230         {
1231           __traits(getMember, parent, c.stringof).update!(__traits(getMember, __traits(getMember, parent, c
1232               .stringof), field));
1233         }
1234         else static if (hasMemberWithInjectOf!(field, c))
1235         {
1236           __traits(getMember, parent, c.stringof).update!(__traits(getMember, __traits(getMember, parent, c
1237               .stringof), field));
1238         }
1239         else
1240 
1241           
1242 
1243             .updateChildren!(member)(__traits(getMember, parent, c.stringof));
1244       }
1245     }
1246   }
1247 }
1248 
1249 auto update(T)(ref T node) if (hasMember!(T, "node"))
1250 {
1251   struct Inner
1252   {
1253   nothrow:
1254     void opDispatch(string name, V)(auto ref V v) const @safe
1255     {
1256       mixin("node.update!(node." ~ name ~ ")(v);");
1257       // NOTE: static assert won't work in opDispatch, as the compiler will not output the string but just ignore the opDispatch call and error out saying missing field on Inner
1258       static if (!hasMember!(T, name))
1259         pragma(msg, "********* Error: " ~ T.stringof ~ " has no property named " ~ name);
1260     }
1261   }
1262 
1263   return Inner();
1264 }
1265 
1266 void update(Range, Sink)(auto ref Range source, ref Sink sink)
1267 {
1268 
1269   auto output = Updater!(Sink)(&sink);
1270   foreach (i; source)
1271     output.put(i);
1272 }
1273 
1274 void setVisible(string field, Parent)(auto ref Parent parent, bool visible)
1275 {
1276   bool current = __traits(getMember, parent, field).node.mounted;
1277   if (current != visible)
1278   {
1279     if (visible)
1280     {
1281       remount!(field)(parent);
1282     }
1283     else
1284     {
1285       unmount(__traits(getMember, parent, field));
1286     }
1287   }
1288 }
1289 
1290 // this will propagate through all the triggers to see if the update caused a state change
1291 @trusted
1292 template update(alias field)
1293 {
1294   import std.traits : isPointer;
1295 
1296   static void updateDom(Parent, T)(auto ref Parent parent, auto ref T t)
1297   {
1298     import memutils.ct : ParameterIdentifierTuple;
1299     import std.traits : hasUDA, isCallable, getUDAs;
1300     import std.meta : AliasSeq;
1301     import std.meta : staticMap;
1302 
1303     alias name = domName!(field.stringof);
1304     static if (hasUDA!(field, prop))
1305     {
1306       parent.node.setPropertyTyped!name(t);
1307     }
1308     else static if (hasUDA!(field, attr))
1309     {
1310       parent.node.setAttributeTyped!name(t);
1311     }
1312     static if (is(T == bool))
1313     {
1314       alias styles = getStyles!(field);
1315       static foreach (style; styles)
1316       {
1317         __gshared static string className = GetCssClassName!(Parent, style);
1318         parent.getNamedNode.changeClass(className, t);
1319       }
1320       static if (hasUDA!(field, visible))
1321       {
1322         alias udas = getUDAs!(field, visible);
1323         static foreach (uda; udas)
1324         {
1325           static if (is(uda : visible!elem, alias elem))
1326           {
1327             // callMember behind the scenes
1328             setVisible!(elem)(parent, __traits(getMember, parent, __traits(identifier, field)));
1329           }
1330         }
1331       }
1332     }
1333     static foreach (i; __traits(allMembers, Parent))
1334     {
1335       {
1336         alias sym = AliasSeq!(__traits(getMember, parent, i))[0];
1337         static if (isCallable!(sym))
1338         {
1339           alias params = ParameterIdentifierTuple!sym;
1340           static if (among!(field, params))
1341           {
1342             static if (hasUDA!(sym, prop))
1343             {
1344               alias cleanName = domName!i;
1345               auto result = callMember!(i)(parent);
1346               parent.node.node.setPropertyTyped!cleanName(result);
1347             }
1348             else static if (hasUDA!(sym, attr))
1349             {
1350               alias cleanName = domName!i;
1351               auto result = callMember!(i)(parent);
1352               parent.node.node.setAttributeTyped!cleanName(result);
1353             }
1354             else static if (hasUDA!(sym, style))
1355             {
1356               auto result = callMember!(i)(parent);
1357               static foreach (style; getStyles!(sym))
1358               {
1359                 __gshared static string className = GetCssClassName!(Parent, style);
1360                 parent.node.node.changeClass(className, result);
1361               }
1362             }
1363             else
1364             {
1365               import std.traits : ReturnType;
1366 
1367               alias RType = ReturnType!(__traits(getMember, parent, i));
1368               static if (is(RType : void))
1369                 callMember!(i)(parent);
1370               else
1371               {
1372                 auto result = callMember!(i)(parent);
1373                 static if (hasUDA!(sym, visible))
1374                 {
1375                   alias udas = getUDAs!(sym, visible);
1376                   static foreach (uda; udas)
1377                   {
1378                     static if (is(uda : visible!elem, alias elem))
1379                     {
1380                       setVisible!(elem)(parent, result);
1381                     }
1382                   }
1383                 }
1384               }
1385             }
1386           }
1387         }
1388       }
1389     }
1390     updateChildren!(field)(parent);
1391   }
1392 
1393   static void update(Parent)(scope auto ref Parent parent)
1394   {
1395     if (!parent.node.mounted)
1396       return;
1397     static if (isPointer!Parent)
1398       updateDom(*parent, __traits(getMember, parent, field.stringof));
1399     else
1400       updateDom(parent, __traits(getMember, parent, field.stringof));
1401   }
1402 
1403   static void update(Parent, T)(scope auto ref Parent parent, T t)
1404   {
1405     mixin("parent." ~ field.stringof ~ " = t;");
1406     if (!parent.node.mounted)
1407       return;
1408     static if (isPointer!Parent)
1409       updateDom(*parent, t);
1410     else
1411       updateDom(parent, t);
1412   }
1413 }
1414 
1415 template symbolFromAliasThis(Parent, string name)
1416 {
1417   import std.meta : anySatisfy;
1418 
1419   alias aliasThises = AliasSeq!(__traits(getAliasThis, Parent));
1420   static if (aliasThises.length == 0)
1421     enum symbolFromAliasThis = false;
1422   else
1423   {
1424     alias hasSymbol = ApplyRight!(hasMember, name);
1425     enum symbolFromAliasThis = anySatisfy!(hasSymbol, aliasThises);
1426   }
1427 }
1428 
1429 auto setAttributeTyped(string name, T)(Handle node, auto ref T t)
1430 {
1431   import std.traits : isPointer;
1432 
1433   static if (isPointer!T)
1434   {
1435     if (t !is null)
1436       node.setAttributeTyped!name(*t);
1437   }
1438   else static if (is(T == bool))
1439     node.setAttributeBool(name, t);
1440   else static if (is(T : int))
1441   {
1442     node.setAttributeInt(name, t);
1443   }
1444   else
1445   {
1446     node.setAttribute(name, t);
1447   }
1448 }
1449 
1450 auto setPropertyTyped(string name, T)(Handle node, auto ref T t)
1451 {
1452   import std.traits : isPointer, isNumeric;
1453 
1454   static if (isPointer!T)
1455   {
1456     if (t !is null)
1457       node.setPropertyTyped!name(*t);
1458   }
1459   else static if (is(T == bool))
1460     node.setPropertyBool(name, t);
1461   else static if (isIntegral!(T))
1462     node.setPropertyInt(name, t);
1463   else static if (isNumeric!(T))
1464     node.setPropertyDouble(name, t);
1465   else
1466   {
1467     static if (__traits(compiles, __traits(getMember, api, name)))
1468       __traits(getMember, api, name)(node, t);
1469     else
1470       node.setProperty(name, t);
1471   }
1472 }
1473 
1474 auto applyStyles(T, styles...)(Handle node)
1475 {
1476   static foreach (style; styles)
1477   {
1478     node.addClass(GetCssClassName!(T, style));
1479   }
1480 }
1481 
1482 Handle createNode(T)(Handle parent, ref T t)
1483 {
1484   enum hasNode = hasMember!(T, "node");
1485   static if (hasNode && is(typeof(t.node) : NamedNode!tag, alias tag))
1486   {
1487     mixin("NodeType n = NodeType." ~ tag ~ ";");
1488     return createElement(n);
1489   }
1490   else
1491     return parent;
1492 }
1493 
1494 template indexOfPred(alias Pred, TList...)
1495 {
1496   enum indexOfPred = indexOf!(Pred, TList).index;
1497 }
1498 
1499 template indexOf(alias Pred, args...)
1500 {
1501   import std.meta : AliasSeq;
1502 
1503   static if (args.length > 0)
1504   {
1505     static if (Pred!(args[0]))
1506     {
1507       enum index = 0;
1508     }
1509     else
1510     {
1511       enum next = indexOf!(Pred, AliasSeq!(args[1 .. $])).index;
1512       enum index = (next == -1) ? -1 : 1 + next;
1513     }
1514   }
1515   else
1516   {
1517     enum index = -1;
1518   }
1519 }
1520 
1521 template domName(string name)
1522 {
1523   static if (name[$ - 1] == '_')
1524     enum domName = name[0 .. $ - 1];
1525   else
1526     enum domName = name;
1527 }
1528 
1529 template join(Seq...)
1530 {
1531   static if (is(typeof(Seq) == string))
1532     enum join = Seq;
1533   else
1534   {
1535     static if (Seq.length == 1)
1536     {
1537       enum join = Seq[0];
1538     }
1539     else
1540     {
1541       enum join = Seq[0] ~ "," ~ join!(Seq[1 .. $]);
1542     }
1543   }
1544 }