diff --git a/cgi.d b/cgi.d
index 83df269..e8d4e00 100644
--- a/cgi.d
+++ b/cgi.d
@@ -3277,6 +3277,37 @@ mixin template GenericMain(alias fun, long maxContentLength = defaultMaxContentL
mixin CustomCgiMain!(Cgi, fun, maxContentLength);
}
+/++
+ Boilerplate mixin for a main function that uses the [dispatcher] function.
+
+ You can send `typeof(null)` as the `Presenter` argument to use a generic one.
+
+ History:
+ Added July 9, 2021
++/
+mixin template DispatcherMain(Presenter, DispatcherArgs...) {
+ /++
+ Handler to the generated presenter you can use from your objects, etc.
+ +/
+ Presenter activePresenter;
+
+ /++
+ Request handler that creates the presenter then forwards to the [dispatcher] function.
+ Renders 404 if the dispatcher did not handle the request.
+ +/
+ void handler(Cgi cgi) {
+ auto presenter = new Presenter;
+ activePresenter = presenter;
+ scope(exit) activePresenter = null;
+
+ if(cgi.dispatcher!DispatcherArgs(presenter))
+ return;
+
+ presenter.renderBasicError(cgi, 404);
+ }
+ mixin GenericMain!handler;
+}
+
private string simpleHtmlEncode(string s) {
return s.replace("&", "&").replace("<", "<").replace(">", ">").replace("\n", "
\n");
}
@@ -4068,6 +4099,17 @@ void handleCgiRequest(alias fun, CustomCgi = Cgi, long maxContentLength = defaul
+/
+/++
+ The stack size when a fiber is created. You can set this from your main or from a shared static constructor
+ to optimize your memory use if you know you don't need this much space. Be careful though, some functions use
+ more stack space than you realize and a recursive function (including ones like in dom.d) can easily grow fast!
+
+ History:
+ Added July 10, 2021. Previously, it used the druntime default of 16 KB.
++/
+version(cgi_use_fiber)
+__gshared size_t fiberStackSize = 4096 * 100;
+
version(cgi_use_fiber)
class CgiFiber : Fiber {
private void function(Socket) f_handler;
@@ -4081,8 +4123,7 @@ class CgiFiber : Fiber {
this(void delegate(Socket) handler) {
this.handler = handler;
- // FIXME: stack size
- super(&run);
+ super(&run, fiberStackSize);
}
Socket connection;
@@ -8106,8 +8147,34 @@ auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
*what = T.init;
return true;
} else {
- // could be a child
- if(name[paramName.length] == '.') {
+ // could be a child. gonna allow either obj.field OR obj[field]
+
+ string afterName;
+
+ if(name[paramName.length] == '[') {
+ int count = 1;
+ auto idx = paramName.length + 1;
+ while(idx < name.length && count > 0) {
+ if(name[idx] == '[')
+ count++;
+ else if(name[idx] == ']') {
+ count--;
+ if(count == 0) break;
+ }
+ idx++;
+ }
+
+ if(idx == name.length)
+ return false; // malformed
+
+ auto insideBrackets = name[paramName.length + 1 .. idx];
+ afterName = name[idx + 1 .. $];
+
+ name = name[0 .. paramName.length];
+
+ paramName = insideBrackets;
+
+ } else if(name[paramName.length] == '.') {
paramName = name[paramName.length + 1 .. $];
name = paramName;
int p = 0;
@@ -8117,17 +8184,23 @@ auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
p++;
}
- // set the child member
- switch(paramName) {
- foreach(idx, memberName; __traits(allMembers, T))
- static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
- // data member!
- case memberName:
- return setVariable(name, paramName, &(__traits(getMember, *what, memberName)), value);
- }
- default:
- // ok, not a member
+ afterName = paramName[p .. $];
+ paramName = paramName[0 .. p];
+ } else {
+ return false;
+ }
+
+ if(paramName.length)
+ // set the child member
+ switch(paramName) {
+ foreach(idx, memberName; __traits(allMembers, T))
+ static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
+ // data member!
+ case memberName:
+ return setVariable(name ~ afterName, paramName, &(__traits(getMember, *what, memberName)), value);
}
+ default:
+ // ok, not a member
}
}
@@ -8539,13 +8612,13 @@ html", true, true);
}
/// Multiple responses deconstruct the algebraic type and forward to the appropriate handler at runtime
- void presentSuccessfulReturn(T : MultipleResponses!Types, Types...)(Cgi cgi, T ret, typeof(null) meta, string format) {
+ void presentSuccessfulReturn(T : MultipleResponses!Types, Meta, Types...)(Cgi cgi, T ret, Meta meta, string format) {
bool outputted = false;
foreach(index, type; Types) {
if(ret.contains == index) {
assert(!outputted);
outputted = true;
- (cast(CRTP) this).presentSuccessfulReturnAsHtml(cgi, ret.payload[index], meta);
+ (cast(CRTP) this).presentSuccessfulReturn(cgi, ret.payload[index], meta, format);
}
}
if(!outputted)
@@ -8655,7 +8728,19 @@ html", true, true);
auto div = Element.make("div");
div.addClass("form-field");
- static if(is(T == struct)) {
+ static if(is(T == Cgi.UploadedFile)) {
+ Element lbl;
+ if(displayName !is null) {
+ lbl = div.addChild("label");
+ lbl.addChild("span", displayName, "label-text");
+ lbl.appendText(" ");
+ } else {
+ lbl = div;
+ }
+ auto i = lbl.addChild("input", name);
+ i.attrs.name = name;
+ i.attrs.type = "file";
+ } else static if(is(T == struct)) {
if(displayName !is null)
div.addChild("span", displayName, "label-text");
auto fieldset = div.addChild("fieldset");
diff --git a/dom.d b/dom.d
index 9604eeb..69b2613 100644
--- a/dom.d
+++ b/dom.d
@@ -94,7 +94,10 @@ bool isConvenientAttribute(string name) {
/// The main document interface, including a html parser.
/// Group: core_functionality
-class Document : FileResource {
+class Document : FileResource, DomParent {
+ inout(Document) asDocument() inout { return this; }
+ inout(Element) asElement() inout { return null; }
+
/// Convenience method for web scraping. Requires [arsd.http2] to be
/// included in the build as well as [arsd.characterencodings].
static Document fromUrl()(string url, bool strictMode = false) {
@@ -1130,6 +1133,7 @@ class Document : FileResource {
} while (r.type != 0 || r.element.nodeType != 1); // we look past the xml prologue and doctype; root only begins on a regular node
root = r.element;
+ root.parent_ = this;
if(!strict) // in strict mode, we'll just ignore stuff after the xml
while(r.type != 4) {
@@ -1353,7 +1357,6 @@ class Document : FileResource {
name = name.toLower();
auto e = Element.make(name, null, null, selfClosedElements);
- e.parentDocument = this;
return e;
@@ -1475,9 +1478,17 @@ class Document : FileResource {
}
}
+interface DomParent {
+ inout(Document) asDocument() inout;
+ inout(Element) asElement() inout;
+}
+
/// This represents almost everything in the DOM.
/// Group: core_functionality
-class Element {
+class Element : DomParent {
+ inout(Document) asDocument() inout { return null; }
+ inout(Element) asElement() inout { return this; }
+
/// Returns a collection of elements by selector.
/// See: [Document.opIndex]
ElementCollection opIndex(string selector) {
@@ -1926,44 +1937,64 @@ class Element {
/// Instead, this flag tells if it should be. It is based on the source document's notation and a html element list.
private bool selfClosed;
+ private DomParent parent_;
+
/// Get the parent Document object that contains this element.
/// It may be null, so remember to check for that.
- Document parentDocument;
+ @property inout(Document) parentDocument() inout {
+ if(this.parent_ is null)
+ return null;
+ auto p = cast() this.parent_.asElement;
+ auto prev = cast() this;
+ while(p) {
+ prev = p;
+ if(p.parent_ is null)
+ return null;
+ p = cast() p.parent_.asElement;
+ }
+ return cast(inout) prev.parent_.asDocument;
+ }
+
+ deprecated @property void parentDocument(Document doc) {
+ parent_ = doc;
+ }
///.
inout(Element) parentNode() inout {
- auto p = _parentNode;
+ if(parent_ is null)
+ return null;
+
+ auto p = parent_.asElement;
if(cast(DocumentFragment) p)
- return p._parentNode;
+ return p.parent_.asElement;
return p;
}
//protected
Element parentNode(Element e) {
- return _parentNode = e;
+ parent_ = e;
+ return e;
}
- private Element _parentNode;
-
- // the next few methods are for implementing interactive kind of things
- private CssStyle _computedStyle;
-
// these are here for event handlers. Don't forget that this library never fires events.
// (I'm thinking about putting this in a version statement so you don't have the baggage. The instance size of this class is 56 bytes right now.)
- EventHandler[][string] bubblingEventHandlers;
- EventHandler[][string] capturingEventHandlers;
- EventHandler[string] defaultEventHandlers;
- void addEventListener(string event, EventHandler handler, bool useCapture = false) {
- if(event.length > 2 && event[0..2] == "on")
- event = event[2 .. $];
+ version(dom_with_events) {
+ EventHandler[][string] bubblingEventHandlers;
+ EventHandler[][string] capturingEventHandlers;
+ EventHandler[string] defaultEventHandlers;
- if(useCapture)
- capturingEventHandlers[event] ~= handler;
- else
- bubblingEventHandlers[event] ~= handler;
+ void addEventListener(string event, EventHandler handler, bool useCapture = false) {
+ if(event.length > 2 && event[0..2] == "on")
+ event = event[2 .. $];
+
+ if(useCapture)
+ capturingEventHandlers[event] ~= handler;
+ else
+ bubblingEventHandlers[event] ~= handler;
+ }
}
@@ -2091,7 +2122,6 @@ class Element {
/// Generally, you don't want to call this yourself - use Element.make or document.createElement instead.
this(Document _parentDocument, string _tagName, string[string] _attributes = null, bool _selfClosed = false) {
- parentDocument = _parentDocument;
tagName = _tagName;
if(_attributes !is null)
attributes = _attributes;
@@ -2128,8 +2158,6 @@ class Element {
}
private this(Document _parentDocument) {
- parentDocument = _parentDocument;
-
version(dom_node_indexes)
this.dataset.nodeIndex = to!string(&(this.attributes));
}
@@ -2600,6 +2628,10 @@ class Element {
// if you change something here, it won't apply... FIXME const? but changing it would be nice if it applies to the style attribute too though you should use style there.
+
+ // the next few methods are for implementing interactive kind of things
+ private CssStyle _computedStyle;
+
/// Don't use this.
@property CssStyle computedStyle() {
if(_computedStyle is null) {
@@ -2713,12 +2745,16 @@ class Element {
selfClosed = false;
e.parentNode = this;
- e.parentDocument = this.parentDocument;
if(auto frag = cast(DocumentFragment) e)
children ~= frag.children;
else
children ~= e;
+ /+
+ foreach(item; e.tree)
+ item.parentDocument = this.parentDocument;
+ +/
+
sendObserverEvent(DomMutationOperations.appendChild, null, null, e);
return e;
@@ -2746,7 +2782,6 @@ class Element {
children = children[0..i] ~ frag.children ~ children[i..$];
else
children = children[0..i] ~ what ~ children[i..$];
- what.parentDocument = this.parentDocument;
what.parentNode = this;
return what;
}
@@ -2781,7 +2816,6 @@ class Element {
else
children = children[0 .. i + 1] ~ what ~ children[i + 1 .. $];
what.parentNode = this;
- what.parentDocument = this.parentDocument;
return what;
}
}
@@ -2810,7 +2844,6 @@ class Element {
c.parentNode = null;
c = replacement;
c.parentNode = this;
- c.parentDocument = this.parentDocument;
return child;
}
assert(0);
@@ -2888,7 +2921,6 @@ class Element {
else
children = children[0..i] ~ child ~ children[i..$];
child.parentNode = this;
- child.parentDocument = this.parentDocument;
break;
}
}
@@ -2920,7 +2952,6 @@ class Element {
do {
foreach(c; e.children) {
c.parentNode = this;
- c.parentDocument = this.parentDocument;
}
if(position is null)
children ~= e.children;
@@ -2954,7 +2985,6 @@ class Element {
}
do {
e.parentNode = this;
- e.parentDocument = this.parentDocument;
if(auto frag = cast(DocumentFragment) e)
children = e.children ~ children;
else
@@ -3000,13 +3030,10 @@ class Element {
doc.parseUtf8("