diff --git a/README.md b/README.md
index 7114bf6..fbf3e23 100644
--- a/README.md
+++ b/README.md
@@ -200,3 +200,7 @@ There's a few other hidden gems in the files themselves, and so much more on my
(Yes, I'm writing a pair of D games again, finally! First time in a long time, but it is moving along well... and I don't need SDL this time!)
+
+# Special Conventions
+
+idl Starting in 2019, I will be adding version info to individual modules.
diff --git a/cgi.d b/cgi.d
index 2d3a872..6b979b6 100644
--- a/cgi.d
+++ b/cgi.d
@@ -1,6 +1,8 @@
// FIXME: if an exception is thrown, we shouldn't necessarily cache...
// FIXME: there's some annoying duplication of code in the various versioned mains
+// FIXME: cgi per-request arena allocator
+
// FIXME: I might make a cgi proxy class which can change things; the underlying one is still immutable
// but the later one can edit and simplify the api. You'd have to use the subclass tho!
@@ -621,9 +623,6 @@ class Cgi {
environmentVariables = cast(const) environment.toAA;
- string[] allPostNamesInOrder;
- string[] allPostValuesInOrder;
-
foreach(arg; args[1 .. $]) {
if(arg.startsWith("--")) {
nextArgIs = arg[2 .. $];
@@ -3757,8 +3756,13 @@ class ListeningConnectionManager {
// certainly does for small requests, and I think it does for larger ones too
sn.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1);
}
- while(queueLength >= queue.length)
+ // wait until a slot opens up
+ //int waited = 0;
+ while(queueLength >= queue.length) {
Thread.sleep(1.msecs);
+ //waited ++;
+ }
+ //if(waited) {import std.stdio; writeln(waited);}
synchronized(this) {
queue[nextIndexBack] = sn;
nextIndexBack++;
@@ -3766,11 +3770,15 @@ class ListeningConnectionManager {
}
semaphore.notify();
+ bool hasAnyRunning;
foreach(thread; threads) {
if(!thread.isRunning) {
thread.join();
- }
+ } else hasAnyRunning = true;
}
+
+ if(!hasAnyRunning)
+ break;
}
// FIXME: i typically stop this with ctrl+c which never
@@ -3815,6 +3823,7 @@ class ListeningConnectionManager {
tcp = true;
}
+ Thread.getThis.priority = Thread.PRIORITY_MAX;
listener.listen(128);
}
@@ -3871,7 +3880,7 @@ class ConnectionThread : Thread {
}
try
dg(socket);
- catch(Exception e)
+ catch(Throwable e)
socket.close();
}
}
@@ -5069,7 +5078,7 @@ class Session(Data) {
cgi.setCookie(
"sessionId", sessionId,
0 /* expiration */,
- null /* path */,
+ "/" /* path */,
null /* domain */,
true /* http only */,
cgi.https /* if the session is started on https, keep it there, otherwise, be flexible */);
@@ -5913,24 +5922,21 @@ ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) {
switch to choose if you want to override.
*/
-struct DispatcherDefinition(alias dispatchHandler) {// if(is(typeof(dispatchHandler("str", Cgi.init, void) == bool))) { // bool delegate(string urlPrefix, Cgi cgi) dispatchHandler;
+struct DispatcherDefinition(alias dispatchHandler, DispatcherDetails = typeof(null)) {// if(is(typeof(dispatchHandler("str", Cgi.init, void) == bool))) { // bool delegate(string urlPrefix, Cgi cgi) dispatchHandler;
alias handler = dispatchHandler;
string urlPrefix;
bool rejectFurther;
- DispatcherDetails details;
-}
-
-// tbh I am really unhappy with this part
-struct DispatcherDetails {
- string filename;
- string contentType;
+ immutable(DispatcherDetails) details;
}
private string urlify(string name) {
- return name;
+ return beautify(name, '-', true);
}
-private string beautify(string name) {
+private string beautify(string name, char space = ' ', bool allLowerCase = false) {
+ if(name == "id")
+ return allLowerCase ? name : "ID";
+
char[160] buffer;
int bufferIndex = 0;
bool shouldCap = true;
@@ -5939,7 +5945,7 @@ private string beautify(string name) {
foreach(idx, char ch; name) {
if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
- if(ch >= 'A' && ch <= 'Z') {
+ if((ch >= 'A' && ch <= 'Z') || ch == '_') {
if(lastWasCap) {
// two caps in a row, don't change. Prolly acronym.
} else {
@@ -5948,209 +5954,27 @@ private string beautify(string name) {
}
lastWasCap = true;
+ } else {
+ lastWasCap = false;
}
if(shouldSpace) {
- buffer[bufferIndex++] = ' ';
+ buffer[bufferIndex++] = space;
if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
+ shouldSpace = false;
}
if(shouldCap) {
if(ch >= 'a' && ch <= 'z')
ch -= 32;
shouldCap = false;
}
+ if(allLowerCase && ch >= 'A' && ch <= 'Z')
+ ch += 32;
buffer[bufferIndex++] = ch;
}
return buffer[0 .. bufferIndex].idup;
}
-/+
- Argument conversions: for the most part, it is to!Thing(string).
-
- But arrays and structs are a bit different. Arrays come from the cgi array. Thus
- they are passed
-
- arr=foo&arr=bar <-- notice the same name.
-
- Structs are first declared with an empty thing, then have their members set individually,
- with dot notation. The members are not required, just the initial declaration.
-
- struct Foo {
- int a;
- string b;
- }
- void test(Foo foo){}
-
- foo&foo.a=5&foo.b=str <-- the first foo declares the arg, the others set the members
-
- Arrays of structs use this declaration.
-
- void test(Foo[] foo) {}
-
- foo&foo.a=5&foo.b=bar&foo&foo.a=9
-
- You can use a hidden input field in HTML forms to achieve this. The value of the naked name
- declaration is ignored.
-
- Mind that order matters! The declaration MUST come first in the string.
-
- Arrays of struct members follow this rule recursively.
-
- struct Foo {
- int[] a;
- }
-
- foo&foo.a=1&foo.a=2&foo&foo.a=1
-
-
- Associative arrays are formatted with brackets, after a declaration, like structs:
-
- foo&foo[key]=value&foo[other_key]=value
-
-
- Note: for maximum compatibility with outside code, keep your types simple. Some libraries
- do not support the strict ordering requirements to work with these struct protocols.
-
- FIXME: also perhaps accept application/json to better work with outside trash.
-
-
- Return values are also auto-formatted according to user-requested type:
- for json, it loops over and converts.
- for html, basic types are strings. Arrays are
. Structs are
. Arrays of structs are tables!
-+/
-
-// returns an arsd.dom.Element
-static auto elementFor(T)(string displayName, string name) {
- import arsd.dom;
- import std.traits;
-
- auto div = Element.make("div");
- div.addClass("form-field");
-
- static if(is(T == struct)) {
- if(displayName !is null)
- div.addChild("span", displayName, "label-text");
- auto fieldset = div.addChild("fieldset");
- fieldset.addChild("legend", beautify(T.stringof)); // FIXME
- fieldset.addChild("input", name);
- static foreach(idx, memberName; __traits(allMembers, T))
- static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
- fieldset.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(beautify(memberName), name ~ "." ~ memberName));
- }
- } else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
- 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;
- static if(isSomeString!T)
- i.attrs.type = "text";
- else
- i.attrs.type = "number";
- i.attrs.value = to!string(T.init);
- } else static if(is(T == bool)) {
- 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.type = "checkbox";
- i.attrs.name = name;
- } else static if(is(T == K[], K)) {
- auto templ = div.addChild("template");
- templ.appendChild(elementFor!(K)(null, name));
- if(displayName !is null)
- div.addChild("span", displayName, "label-text");
- auto btn = div.addChild("button");
- btn.addClass("add-array-button");
- btn.attrs.type = "button";
- btn.innerText = "Add";
- btn.attrs.onclick = q{
- var a = document.importNode(this.parentNode.firstChild.content, true);
- this.parentNode.insertBefore(a, this);
- };
- } else static if(is(T == V[K], K, V)) {
- div.innerText = "assoc array not implemented for automatic form at this time";
- } else {
- static assert(0, "unsupported type for cgi call " ~ T.stringof);
- }
-
-
- return div;
-}
-
-// actually returns an arsd.dom.Form
-auto createAutomaticFormForFunction(alias method, T)(T dg) {
- import arsd.dom;
-
- auto form = cast(Form) Element.make("form");
-
- form.addClass("automatic-form");
-
- form.addChild("h3", beautify(__traits(identifier, method)));
-
- import std.traits;
-
- //Parameters!method params;
- //alias idents = ParameterIdentifierTuple!method;
- //alias defaults = ParameterDefaults!method;
-
- static if(is(typeof(method) P == __parameters))
- static foreach(idx, _; P) {{
- alias param = P[idx .. idx + 1];
- string displayName = beautify(__traits(identifier, param));
- static foreach(attr; __traits(getAttributes, param))
- static if(is(typeof(attr) == DisplayName))
- displayName = attr.name;
- form.appendChild(elementFor!(param)(displayName, __traits(identifier, param)));
- }}
-
- form.addChild("div", Html(``), "submit-button-holder");
-
- return form;
-}
-
-// actually returns an arsd.dom.Form
-auto createAutomaticFormForObject(T)(T obj) {
- import arsd.dom;
-
- auto form = cast(Form) Element.make("form");
-
- form.addClass("automatic-form");
-
- form.addChild("h3", beautify(__traits(identifier, T)));
-
- import std.traits;
-
- //Parameters!method params;
- //alias idents = ParameterIdentifierTuple!method;
- //alias defaults = ParameterDefaults!method;
-
- static foreach(idx, memberName; __traits(derivedMembers, T)) {{
- static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
- string displayName = beautify(memberName);
- static foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName)))
- static if(is(typeof(attr) == DisplayName))
- displayName = attr.name;
- form.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(displayName, memberName));
-
- form.setValue(memberName, to!string(__traits(getMember, obj, memberName)));
- }}}
-
- form.addChild("div", Html(``), "submit-button-holder");
-
- return form;
-}
-
/*
string urlFor(alias func)() {
return __traits(identifier, func);
@@ -6189,6 +6013,7 @@ class MissingArgumentException : Exception {
}
}
+// it only looks at query params for GET requests, the rest must be in the body for a function argument.
auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
// FIXME: any array of structs should also be settable or gettable from csv as well.
@@ -6206,20 +6031,24 @@ auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
// first, check for missing arguments and initialize to defaults if necessary
static foreach(idx, param; params) {{
- auto ident = idents[idx];
- if(cgi.requestMethod == Cgi.RequestMethod.POST) {
- if(ident !in cgi.post) {
- static if(is(defaults[idx] == void))
- throw new MissingArgumentException(__traits(identifier, method), ident, typeof(param).stringof);
- else
- params[idx] = defaults[idx];
- }
+ static if(is(typeof(param) : Cgi)) {
+ params[idx] = cgi;
} else {
- if(ident !in cgi.get) {
- static if(is(defaults[idx] == void))
- throw new MissingArgumentException(__traits(identifier, method), ident, typeof(param).stringof);
- else
- params[idx] = defaults[idx];
+ auto ident = idents[idx];
+ if(cgi.requestMethod == Cgi.RequestMethod.GET) {
+ if(ident !in cgi.get) {
+ static if(is(defaults[idx] == void))
+ throw new MissingArgumentException(__traits(identifier, method), ident, typeof(param).stringof);
+ else
+ params[idx] = defaults[idx];
+ }
+ } else {
+ if(ident !in cgi.post) {
+ static if(is(defaults[idx] == void))
+ throw new MissingArgumentException(__traits(identifier, method), ident, typeof(param).stringof);
+ else
+ params[idx] = defaults[idx];
+ }
}
}
}}
@@ -6330,21 +6159,25 @@ auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
sw: switch(paramName) {
static foreach(idx, param; params) {
- case idents[idx]:
- setVariable(name, paramName, ¶ms[idx], value);
- break sw;
+ static if(is(typeof(param) : Cgi)) {
+ // cannot be set from the outside
+ } else {
+ case idents[idx]:
+ setVariable(name, paramName, ¶ms[idx], value);
+ break sw;
+ }
}
default:
// ignore; not relevant argument
}
}
- if(cgi.requestMethod == Cgi.RequestMethod.POST) {
- names = cgi.allPostNamesInOrder;
- values = cgi.allPostValuesInOrder;
- } else {
+ if(cgi.requestMethod == Cgi.RequestMethod.GET) {
names = cgi.allGetNamesInOrder;
values = cgi.allGetValuesInOrder;
+ } else {
+ names = cgi.allPostNamesInOrder;
+ values = cgi.allPostValuesInOrder;
}
foreach(idx, name; names) {
@@ -6364,94 +6197,60 @@ auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
return ret;
}
-auto formatReturnValueAsHtml(T)(T t) {
- import arsd.dom;
- import std.traits;
+/+
+ Argument conversions: for the most part, it is to!Thing(string).
- static if(is(T == typeof(null))) {
- return Element.make("span");
- } else static if(isIntegral!T || isSomeString!T || isFloatingPoint!T) {
- return Element.make("span", to!string(t), "automatic-data-display");
- } else static if(is(T == V[K], K, V)) {
- auto dl = Element.make("dl");
- dl.addClass("automatic-data-display");
- foreach(k, v; t) {
- dl.addChild("dt", to!string(k));
- dl.addChild("dd", formatReturnValueAsHtml(v));
- }
- return dl;
- } else static if(is(T == struct)) {
- auto dl = Element.make("dl");
- dl.addClass("automatic-data-display");
+ But arrays and structs are a bit different. Arrays come from the cgi array. Thus
+ they are passed
- static foreach(idx, memberName; __traits(allMembers, T))
- static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
- dl.addChild("dt", memberName);
- dl.addChild("dt", formatReturnValueAsHtml(__traits(getMember, t, memberName)));
- }
+ arr=foo&arr=bar <-- notice the same name.
- return dl;
- } else static if(is(T == bool)) {
- return Element.make("span", t ? "true" : "false", "automatic-data-display");
- } else static if(is(T == E[], E)) {
- static if(is(E : RestObject!Proxy, Proxy)) {
- // treat RestObject similar to struct
- auto table = cast(Table) Element.make("table");
- table.addClass("automatic-data-display");
- string[] names;
- static foreach(idx, memberName; __traits(derivedMembers, E))
- static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
- names ~= beautify(memberName);
- }
- table.appendHeaderRow(names);
+ Structs are first declared with an empty thing, then have their members set individually,
+ with dot notation. The members are not required, just the initial declaration.
- foreach(l; t) {
- auto tr = table.appendRow();
- static foreach(idx, memberName; __traits(derivedMembers, E))
- static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
- static if(memberName == "id") {
- string val = to!string(__traits(getMember, l, memberName));
- tr.addChild("td", Element.make("a", val, E.stringof.toLower ~ "s/" ~ val)); // FIXME
- } else {
- tr.addChild("td", formatReturnValueAsHtml(__traits(getMember, l, memberName)));
- }
- }
- }
+ struct Foo {
+ int a;
+ string b;
+ }
+ void test(Foo foo){}
- return table;
- } else static if(is(E == struct)) {
- // an array of structs is kinda special in that I like
- // having those formatted as tables.
- auto table = cast(Table) Element.make("table");
- table.addClass("automatic-data-display");
- string[] names;
- static foreach(idx, memberName; __traits(allMembers, E))
- static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
- names ~= beautify(memberName);
- }
- table.appendHeaderRow(names);
+ foo&foo.a=5&foo.b=str <-- the first foo declares the arg, the others set the members
- foreach(l; t) {
- auto tr = table.appendRow();
- static foreach(idx, memberName; __traits(allMembers, E))
- static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
- tr.addChild("td", formatReturnValueAsHtml(__traits(getMember, l, memberName)));
- }
- }
+ Arrays of structs use this declaration.
- return table;
- } else {
- // otherwise, I will just make a list.
- auto ol = Element.make("ol");
- ol.addClass("automatic-data-display");
- foreach(e; t)
- ol.addChild("li", formatReturnValueAsHtml(e));
- return ol;
- }
- } else static assert(0, "bad return value for cgi call " ~ T.stringof);
+ void test(Foo[] foo) {}
- assert(0);
-}
+ foo&foo.a=5&foo.b=bar&foo&foo.a=9
+
+ You can use a hidden input field in HTML forms to achieve this. The value of the naked name
+ declaration is ignored.
+
+ Mind that order matters! The declaration MUST come first in the string.
+
+ Arrays of struct members follow this rule recursively.
+
+ struct Foo {
+ int[] a;
+ }
+
+ foo&foo.a=1&foo.a=2&foo&foo.a=1
+
+
+ Associative arrays are formatted with brackets, after a declaration, like structs:
+
+ foo&foo[key]=value&foo[other_key]=value
+
+
+ Note: for maximum compatibility with outside code, keep your types simple. Some libraries
+ do not support the strict ordering requirements to work with these struct protocols.
+
+ FIXME: also perhaps accept application/json to better work with outside trash.
+
+
+ Return values are also auto-formatted according to user-requested type:
+ for json, it loops over and converts.
+ for html, basic types are strings. Arrays are . Structs are
. Arrays of structs are tables!
++/
/++
A web presenter is responsible for rendering things to HTML to be usable
@@ -6459,9 +6258,34 @@ auto formatReturnValueAsHtml(T)(T t) {
They are passed as template arguments to the base classes of [WebObject]
- FIXME
+ Responsible for displaying stuff as HTML. You can put this into your own aggregate
+ and override it. Use forwarding and specialization to customize it.
+
+ When you inherit from it, pass your own class as the CRTP argument. This lets the base
+ class templates and your overridden templates work with each other.
+
+ ---
+ class MyPresenter : WebPresenter!(MyPresenter) {
+ @Override
+ void presentSuccessfulReturnAsHtml(T : CustomType)(Cgi cgi, T ret) {
+ // present the CustomType
+ }
+ @Override
+ void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret) {
+ // handle everything else via the super class, which will call
+ // back to your class when appropriate
+ super.presentSuccessfulReturnAsHtml(cgi, ret);
+ }
+ }
+ ---
+
+/
-class WebPresenter() {
+class WebPresenter(CRTP) {
+
+ /// A UDA version of the built-in `override`, to be used for static template polymorphism
+ /// If you override a plain method, use `override`. If a template, use `@Override`.
+ enum Override;
+
string script() {
return `
`;
@@ -6479,7 +6303,8 @@ class WebPresenter() {
}
string genericFormStyling() {
- return `
+ return
+q"css
table.automatic-data-display {
border-collapse: collapse;
border: solid 1px var(--mild-border);
@@ -6521,28 +6346,29 @@ class WebPresenter() {
.add-array-button {
}
- `;
+css";
}
string genericSiteStyling() {
- return `
+ return
+q"css
* { box-sizing: border-box; }
html, body { margin: 0px; }
body {
font-family: sans-serif;
}
- #header {
+ header {
background: var(--accent-color);
height: 64px;
}
- #footer {
+ footer {
background: var(--accent-color);
height: 64px;
}
- #main-site {
+ #site-container {
display: flex;
}
- #container {
+ main {
flex: 1 1 auto;
order: 2;
min-height: calc(100vh - 64px - 64px);
@@ -6554,51 +6380,453 @@ class WebPresenter() {
order: 1;
background: var(--sidebar-color);
}
- `;
+css";
}
import arsd.dom;
Element htmlContainer() {
- auto document = new Document(`
+ auto document = new Document(q"html
+
D Application
-
-