This commit is contained in:
Adam D. Ruppe 2020-09-07 22:32:53 -04:00
parent 6b8c5a55bd
commit ea88731d58
9 changed files with 289 additions and 79 deletions

131
cgi.d
View File

@ -360,6 +360,20 @@ version(Posix) {
}
}
void cloexec(int fd) {
version(Posix) {
import core.sys.posix.fcntl;
fcntl(fd, F_SETFD, FD_CLOEXEC);
}
}
void cloexec(Socket s) {
version(Posix) {
import core.sys.posix.fcntl;
fcntl(s.handle, F_SETFD, FD_CLOEXEC);
}
}
// the servers must know about the connections to talk to them; the interfaces are vital
version(with_addon_servers)
version=with_addon_servers_connections;
@ -1250,6 +1264,8 @@ class Cgi {
// multipart/form-data
// FIXME: this might want to be factored out and factorized
// need to make sure the stream hooks actually work.
void pieceHasNewContent() {
// we just grew the piece's buffer. Do we have to switch to file backing?
if(pps.piece.contentInMemory) {
@ -3197,25 +3213,25 @@ void cgiMainImpl(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxC
version(with_addon_servers)
runWebsocketServer();
else
printf("Add-on servers not compiled in.");
printf("Add-on servers not compiled in.\n");
return;
case "--session-server":
version(with_addon_servers)
runSessionServer();
else
printf("Add-on servers not compiled in.");
printf("Add-on servers not compiled in.\n");
return;
case "--event-server":
version(with_addon_servers)
runEventServer();
else
printf("Add-on servers not compiled in.");
printf("Add-on servers not compiled in.\n");
return;
case "--timer-server":
version(with_addon_servers)
runTimerServer();
else
printf("Add-on servers not compiled in.");
printf("Add-on servers not compiled in.\n");
return;
case "--timed-jobs":
import core.demangle;
@ -3280,6 +3296,8 @@ void cgiMainImpl(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxC
if(sock == -1)
throw new Exception("socket");
cloexec(sock);
{
sockaddr_in addr;
addr.sin_family = AF_INET;
@ -3348,6 +3366,7 @@ void cgiMainImpl(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxC
// the Cgi class does internal buffering, so disabling this
// helps with latency in many cases...
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, opt.sizeof);
cloexec(s);
} else {
int s;
auto readret = read_fd(pipeReadFd, &s, s.sizeof, &s);
@ -3479,6 +3498,7 @@ void cgiMainImpl(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxC
sockaddr addr;
i = addr.sizeof;
s = accept(sock, &addr, &i);
cloexec(s);
import core.sys.posix.netinet.tcp;
int opt = 1;
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, opt.sizeof);
@ -4101,6 +4121,7 @@ class ListeningConnectionManager {
// thing when a request is slow
while(!loopBroken && running) {
auto sn = listener.accept();
cloexec(sn);
try {
handler(sn);
} catch(Exception e) {
@ -4143,6 +4164,7 @@ class ListeningConnectionManager {
void accept_new_connection() {
sn = listener.accept();
cloexec(sn);
if(tcp) {
// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
// on the socket because we do some buffering internally. I think this helps,
@ -4237,6 +4259,7 @@ class ListeningConnectionManager {
if(host.startsWith("unix:")) {
version(Posix) {
listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
cloexec(listener);
string filename = host["unix:".length .. $].idup;
listener.bind(new UnixAddress(filename));
cleanup = delegate() {
@ -4250,6 +4273,7 @@ class ListeningConnectionManager {
} else if(host.startsWith("abstract:")) {
version(linux) {
listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
cloexec(listener);
string filename = "\0" ~ host["abstract:".length .. $];
import std.stdio; stderr.writeln("Listening to abstract unix domain socket: ", host["abstract:".length .. $]);
listener.bind(new UnixAddress(filename));
@ -4259,6 +4283,7 @@ class ListeningConnectionManager {
}
} else {
listener = new TcpSocket();
cloexec(listener);
listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
listener.bind(host.length ? parseAddress(host, port) : new InternetAddress(port));
tcp = true;
@ -5276,13 +5301,13 @@ version(Posix) {
//private:
// template for laziness
void startWebsocketServer()() {
void startAddonServer()(string arg) {
version(linux) {
import core.sys.posix.unistd;
pid_t pid;
const(char)*[16] args;
args[0] = "ARSD_CGI_WEBSOCKET_SERVER";
args[1] = "--websocket-server";
args[1] = arg.ptr;
posix_spawn(&pid, "/proc/self/exe",
null,
null,
@ -5299,10 +5324,12 @@ void startWebsocketServer()() {
startupInfo.cb = cast(DWORD) startupInfo.sizeof;
PROCESS_INFORMATION processInfo;
import std.utf;
// I *MIGHT* need to run it as a new job or a service...
auto ret = CreateProcessW(
filename.ptr,
"--websocket-server"w,
toUTF16z(arg),
null, // process attributes
null, // thread attributes
false, // inherit handles
@ -5383,7 +5410,7 @@ version(Posix) {
}
version(with_addon_servers_connections)
LocalServerConnectionHandle openLocalServerConnection(string name) {
LocalServerConnectionHandle openLocalServerConnection()(string name, string arg) {
version(Posix) {
import core.sys.posix.unistd;
import core.sys.posix.sys.un;
@ -5395,6 +5422,8 @@ LocalServerConnectionHandle openLocalServerConnection(string name) {
scope(failure)
close(sock);
cloexec(sock);
// add-on server processes are assumed to be local, and thus will
// use unix domain sockets. Besides, I want to pass sockets to them,
// so it basically must be local (except for the session server, but meh).
@ -5409,8 +5438,21 @@ LocalServerConnectionHandle openLocalServerConnection(string name) {
addr.sun_path[0 .. name.length] = cast(typeof(addr.sun_path[])) name[];
}
if(connect(sock, cast(sockaddr*) &addr, addr.sizeof) == -1)
throw new Exception("connect " ~ to!string(errno));
bool alreadyTried;
try_again:
if(connect(sock, cast(sockaddr*) &addr, addr.sizeof) == -1) {
if(!alreadyTried && errno == ECONNREFUSED) {
// try auto-spawning the server, then attempt connection again
startAddonServer(arg);
import core.thread;
Thread.sleep(50.msecs);
alreadyTried = true;
goto try_again;
} else
throw new Exception("connect " ~ to!string(errno));
}
return sock;
} else version(Windows) {
@ -5716,7 +5758,7 @@ private immutable void delegate(string[])[string] scheduledJobHandlers;
version(with_breaking_cgi_features)
mixin(q{
mixin template ImplementRpcClientInterface(T, string serverPath) {
mixin template ImplementRpcClientInterface(T, string serverPath, string cmdArg) {
static import std.traits;
// derivedMembers on an interface seems to give exactly what I want: the virtual functions we need to implement. so I am just going to use it directly without more filtering.
@ -5778,7 +5820,7 @@ mixin template ImplementRpcClientInterface(T, string serverPath) {
}
void connect() {
connectionHandle = openLocalServerConnection(serverPath);
connectionHandle = openLocalServerConnection(serverPath, cmdArg);
}
void disconnect() {
@ -5946,7 +5988,7 @@ interface Session(Data) : SessionObject {
static if(is(typeof(__traits(getMember, Data, memberName))))
mixin(q{
@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout;
@property void } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value);
@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value);
});
}
@ -6034,12 +6076,13 @@ class BasicDataServerSession(Data) : Session!Data {
// basically. Assuming the session is POD this should be fine.
return cast(typeof(return)) to!(typeof(__traits(getMember, Data, memberName)))(v);
}
@property void } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
if(sessionId is null)
start();
import std.conv;
import std.traits;
BasicDataServer.connection.setSessionData(sessionId, fullyQualifiedName!Data ~ "." ~ memberName, to!string(value));
return value;
}
});
}
@ -6063,8 +6106,8 @@ class MockSession(Data) : Session!Data {
@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout {
return __traits(getMember, store_, memberName);
}
@property void } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
__traits(getMember, store_, memberName) = value;
@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
return __traits(getMember, store_, memberName) = value;
}
});
}
@ -6098,7 +6141,7 @@ interface BasicDataServer {
version(with_addon_servers_connections)
class BasicDataServerConnection : BasicDataServer {
mixin ImplementRpcClientInterface!(BasicDataServer, "/tmp/arsd_session_server");
mixin ImplementRpcClientInterface!(BasicDataServer, "/tmp/arsd_session_server", "--session-server");
}
version(with_addon_servers)
@ -6272,7 +6315,7 @@ interface ScheduledJobServer {
version(with_addon_servers_connections)
class ScheduledJobServerConnection : ScheduledJobServer {
mixin ImplementRpcClientInterface!(ScheduledJobServer, "/tmp/arsd_scheduled_job_server");
mixin ImplementRpcClientInterface!(ScheduledJobServer, "/tmp/arsd_scheduled_job_server", "--timer-server");
}
version(with_addon_servers)
@ -6409,7 +6452,7 @@ interface EventSourceServer {
cgi.flush();
cgi.closed = true;
auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server");
auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server", "--event-server");
scope(exit)
closeLocalServerConnection(s);
@ -6447,7 +6490,7 @@ interface EventSourceServer {
[sendEventToEventServer]
+/
public static void sendEvent(string url, string event, string data, int lifetime) {
auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server");
auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server", "--event-server");
scope(exit)
closeLocalServerConnection(s);
@ -6737,6 +6780,8 @@ void runAddonServer(EIS)(string localListenerName, EIS eis) if(is(EIS : EventIoS
scope(failure)
close(sock);
cloexec(sock);
// add-on server processes are assumed to be local, and thus will
// use unix domain sockets. Besides, I want to pass sockets to them,
// so it basically must be local (except for the session server, but meh).
@ -6844,6 +6889,7 @@ void runAddonServer(EIS)(string localListenerName, EIS eis) if(is(EIS : EventIoS
}
throw new Exception("accept " ~ to!string(errno));
}
cloexec(ns);
makeNonBlocking(ns);
auto niop = allocateIoOp(ns, IoOp.ReadSocketHandle, 4096, &eis.handleLocalConnectionData);
@ -7082,11 +7128,11 @@ struct DispatcherDefinition(alias dispatchHandler, DispatcherDetails = typeof(nu
immutable(DispatcherDetails) details;
}
private string urlify(string name) {
private string urlify(string name) pure {
return beautify(name, '-', true);
}
private string beautify(string name, char space = ' ', bool allLowerCase = false) {
private string beautify(string name, char space = ' ', bool allLowerCase = false) pure {
if(name == "id")
return allLowerCase ? name : "ID";
@ -7512,11 +7558,11 @@ private bool hasIfCalledFromWeb(attrs...)() {
---
class MyPresenter : WebPresenter!(MyPresenter) {
@Override
void presentSuccessfulReturnAsHtml(T : CustomType)(Cgi cgi, T ret) {
void presentSuccessfulReturnAsHtml(T : CustomType)(Cgi cgi, T ret, typeof(null) meta) {
// present the CustomType
}
@Override
void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret) {
void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, typeof(null) meta) {
// handle everything else via the super class, which will call
// back to your class when appropriate
super.presentSuccessfulReturnAsHtml(cgi, ret);
@ -7524,6 +7570,8 @@ private bool hasIfCalledFromWeb(attrs...)() {
}
---
The meta argument in there can be overridden by your own facility.
+/
class WebPresenter(CRTP) {
@ -7661,26 +7709,30 @@ html", true, true);
cgi.write(c.parentDocument.toString(), true);
}
template methodMeta(alias method) {
enum methodMeta = null;
}
/// typeof(null) (which is also used to represent functions returning `void`) do nothing
/// in the default presenter - allowing the function to have full low-level control over the
/// response.
void presentSuccessfulReturnAsHtml(T : typeof(null))(Cgi cgi, T ret) {
void presentSuccessfulReturnAsHtml(T : typeof(null))(Cgi cgi, T ret, typeof(null) meta) {
// nothing intentionally!
}
/// Redirections are forwarded to [Cgi.setResponseLocation]
void presentSuccessfulReturnAsHtml(T : Redirection)(Cgi cgi, T ret) {
void presentSuccessfulReturnAsHtml(T : Redirection)(Cgi cgi, T ret, typeof(null) meta) {
cgi.setResponseLocation(ret.to, true, getHttpCodeText(ret.code));
}
/// Multiple responses deconstruct the algebraic type and forward to the appropriate handler at runtime
void presentSuccessfulReturnAsHtml(T : MultipleResponses!Types, Types...)(Cgi cgi, T ret) {
void presentSuccessfulReturnAsHtml(T : MultipleResponses!Types, Types...)(Cgi cgi, T ret, typeof(null) meta) {
bool outputted = false;
foreach(index, type; Types) {
if(ret.contains == index) {
assert(!outputted);
outputted = true;
(cast(CRTP) this).presentSuccessfulReturnAsHtml(cgi, ret.payload[index]);
(cast(CRTP) this).presentSuccessfulReturnAsHtml(cgi, ret.payload[index], meta);
}
}
if(!outputted)
@ -7688,14 +7740,14 @@ html", true, true);
}
/// An instance of the [arsd.dom.FileResource] interface has its own content type; assume it is a download of some sort.
void presentSuccessfulReturnAsHtml(T : FileResource)(Cgi cgi, T ret) {
void presentSuccessfulReturnAsHtml(T : FileResource)(Cgi cgi, T ret, typeof(null) meta) {
cgi.setCache(true); // not necessarily true but meh
cgi.setResponseContentType(ret.contentType);
cgi.write(ret.getData(), true);
}
/// And the default handler will call [formatReturnValueAsHtml] and place it inside the [htmlContainer].
void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret) {
void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, typeof(null) meta) {
auto container = this.htmlContainer();
container.appendChild(formatReturnValueAsHtml(ret));
cgi.write(container.parentDocument.toString(), true);
@ -7866,7 +7918,11 @@ html", true, true);
form.addClass("automatic-form");
form.addChild("h3", beautify(__traits(identifier, method)));
string formDisplayName = beautify(__traits(identifier, method));
foreach(attr; __traits(getAttributes, method))
static if(is(typeof(attr) == DisplayName))
formDisplayName = attr.name;
form.addChild("h3", formDisplayName);
import std.traits;
@ -7939,6 +7995,12 @@ html", true, true);
return formatReturnValueAsHtml(t.payload[index]);
}
assert(0);
} else static if(is(T == Paginated!E, E)) {
auto e = Element.make("div").addClass("paginated-result");
e.appendChild(formatReturnValueAsHtml(t.items));
if(t.nextPageUrl.length)
e.appendChild(Element.make("a", "Next Page", t.nextPageUrl));
return e;
} 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)) {
@ -8439,7 +8501,7 @@ private auto serveApiInternal(T)(string urlPrefix) {
// a void return (or typeof(null) lol) means you, the user, is doing it yourself. Gives full control.
try {
auto ret = callFromCgi!(__traits(getOverloads, obj, methodName)[idx])(&(__traits(getOverloads, obj, methodName)[idx]), cgi);
presenter.presentSuccessfulReturnAsHtml(cgi, ret);
presenter.presentSuccessfulReturnAsHtml(cgi, ret, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]));
} catch(Throwable t) {
presenter.presentExceptionAsHtml!(__traits(getOverloads, obj, methodName)[idx])(cgi, t, &(__traits(getOverloads, obj, methodName)[idx]));
}
@ -8506,6 +8568,11 @@ string defaultFormat(alias method)() {
return "html";
}
struct Paginated(T) {
T[] items;
string nextPageUrl;
}
string[] urlNamesForMethod(alias method)(string def) {
auto verb = Cgi.RequestMethod.GET;
bool foundVerb = false;

View File

@ -1,5 +1,8 @@
/++
Base module for working with colors and in-memory image pixmaps.
Also has various basic data type definitions that are generally
useful with images like [Point], [Size], and [Rectangle].
+/
module arsd.color;

2
curl.d
View File

@ -68,7 +68,7 @@ struct CurlOptions {
string getDigestString(string s) {
import std.digest.md;
import std.digest.digest;
import std.digest;
auto hash = md5Of(s);
auto a = toHexString(hash);
return a.idup;

View File

@ -22,7 +22,7 @@
module arsd.htmlwidget;
public import arsd.simpledisplay;
import arsd.png;
import arsd.image;
public import arsd.dom;
@ -122,32 +122,24 @@ class LayoutData {
// legitimate attributes FIXME: do these belong here?
if(element.hasAttribute("colspan"))
tableColspan = to!int(element.colspan);
tableColspan = to!int(element.attrs.colspan);
else
tableColspan = 1;
if(element.hasAttribute("rowspan"))
tableRowspan = to!int(element.rowspan);
tableRowspan = to!int(element.attrs.rowspan);
else
tableRowspan = 1;
if(element.tagName == "img" && element.src().indexOf(".png") != -1) { // HACK
if(element.tagName == "img") {
try {
auto bytes = cast(ubyte[]) curl(absolutizeUrl(element.src, _contextHack.currentUrl));
auto bytesArr = [bytes];
auto png = pngFromBytes(bytesArr);
image = new Image(png.header.width, png.header.height);
auto i = loadImageFromMemory(bytes);
image = Image.fromMemoryImage(i);
width = CssSize(to!string(image.width) ~ "px");
height = CssSize(to!string(image.height) ~ "px");
int y;
foreach(line; png.byRgbaScanline) {
foreach(x, color; line.pixels)
image[x, y] = color;
y++;
}
} catch (Throwable t) {
writeln(t.toString);
image = null;
@ -248,6 +240,7 @@ class LayoutData {
break;
case "table":
renderInline = false;
goto case;
case "inline-table":
tableDisplay = TableDisplay.table;
break;
@ -473,7 +466,7 @@ int longestLine(string a) {
int longest = 0;
foreach(l; a.split("\n"))
if(l.length > longest)
longest = l.length;
longest = cast(int) l.length;
return longest;
}
@ -658,7 +651,7 @@ bool layout(Element element, int containerWidth, int containerHeight, int cx, in
}
if(changed) {
skip = i;
skip = cast(int) i;
writeln("dom changed");
goto startAgain;
}
@ -671,24 +664,32 @@ bool layout(Element element, int containerWidth, int containerHeight, int cx, in
// And finally, layout this element itself
if(element.nodeType == 3) {
l.textToRender = replace(element.nodeValue,"\n", " ").replace("\t", " ").replace("\r", " ").squeeze(" ");
bool wrapIt;
if(element.computedStyle.getValue("white-space") == "pre") {
l.textToRender = element.nodeValue;
} else {
l.textToRender = replace(element.nodeValue,"\n", " ").replace("\t", " ").replace("\r", " ");//.squeeze(" "); // FIXME
wrapIt = true;
}
if(l.textToRender.length == 0) {
l.doNotRender = true;
return false;
}
auto lineWidth = containerWidth / 6;
if(wrapIt) {
auto lineWidth = containerWidth / 6;
bool startedWithSpace = l.textToRender[0] == ' ';
bool startedWithSpace = l.textToRender[0] == ' ';
if(l.textToRender.length > lineWidth)
l.textToRender = wrap(l.textToRender, lineWidth);
if(l.textToRender.length > lineWidth)
l.textToRender = wrap(l.textToRender, lineWidth);
if(l.textToRender[$-1] == '\n')
l.textToRender = l.textToRender[0 .. $-1];
if(l.textToRender[$-1] == '\n')
l.textToRender = l.textToRender[0 .. $-1];
if(startedWithSpace && l.textToRender[0] != ' ')
l.textToRender = " " ~ l.textToRender;
if(startedWithSpace && l.textToRender[0] != ' ')
l.textToRender = " " ~ l.textToRender;
}
bool contentChanged = false;
// we can wrap so let's do it
@ -710,7 +711,7 @@ bool layout(Element element, int containerWidth, int containerHeight, int cx, in
*/
if(l.textToRender.length != 0) {
l.offsetHeight = count(l.textToRender, "\n") * 16 + 16; // lines * line-height
l.offsetHeight = cast(int) count(l.textToRender, "\n") * 16 + 16; // lines * line-height
l.offsetWidth = l.textToRender.longestLine * 6; // inline
} else {
l.offsetWidth = 0;
@ -1192,7 +1193,7 @@ Document gotoSite(SimpleWindow win, BrowsingContext context, string url, string
string styleSheetText = import("default.css");
foreach(ele; document.querySelectorAll("head link[rel=stylesheet]")) {
if(!ele.hasAttribute("media") || ele.media().indexOf("screen") != -1)
if(!ele.hasAttribute("media") || ele.attrs.media().indexOf("screen") != -1)
styleSheetText ~= curl(ele.href.absolutizeUrl(context.currentUrl));
}
@ -1213,6 +1214,6 @@ Document gotoSite(SimpleWindow win, BrowsingContext context, string url, string
string impossible() {
assert(0);
return null;
//return null;
}

38
jsvar.d
View File

@ -80,7 +80,6 @@ module arsd.jsvar;
version=new_std_json;
import std.stdio;
static import std.array;
import std.traits;
import std.conv;
@ -1589,8 +1588,9 @@ struct var {
val.type = JSONType.null_;
} else {
val.type = JSONType.object;
foreach(k, v; _payload._object._properties)
foreach(k, v; _payload._object._properties) {
val.object[k] = v.toJsonValue();
}
}
}
break;
@ -1842,8 +1842,28 @@ class PrototypeObject {
JSONValue toJsonValue() {
JSONValue val;
JSONValue[string] tmp;
foreach(k, v; this._properties)
foreach(k, v; this._properties) {
// if it is an overload set and/or a function, just skip it.
// or really if it is a wrapped native object it should prolly just be skipped anyway
// unless it actually defines a toJson.
if(v.payloadType == var.Type.Function)
continue;
if(v.payloadType == var.Type.Object) {
// I'd love to get the json value out but idk. FIXME
if(auto wno = cast(WrappedNativeObject) v._payload._object) {
auto obj = wno.getObject();
if(obj is null)
tmp[k] = null;
else
tmp[k] = obj.toString();
continue;
} else if(typeid(PrototypeObject) !is typeid(v._payload._object))
continue;
}
tmp[k] = v.toJsonValue();
}
val = tmp;
return val;
}
@ -2544,8 +2564,18 @@ class OverloadSet : PrototypeObject {
// remember script.d supports default args too.
int bestScore = int.min;
Overload bestMatch;
if(overloads.length == 0) {
return var(null);
}
foreach(overload; overloads) {
if(overload.argTypes.length == 0) {
if(arguments.length == 0) {
bestScore = 0;
bestMatch = overload;
break;
}
if(bestScore < 0) {
bestScore = 0;
bestMatch = overload;
@ -2581,7 +2611,7 @@ class OverloadSet : PrototypeObject {
}
if(bestScore < 0)
throw new Exception("no matching overload found");
throw new Exception("no matching overload found");// " ~ to!string(arguments) ~ " " ~ to!string(overloads));
return bestMatch.func.apply(this_, arguments);

2
png.d
View File

@ -1870,7 +1870,7 @@ void writePngLazy(OutputRange, InputRange)(ref OutputRange where, InputRange ima
// bKGD - palette entry for background or the RGB (16 bits each) for that. or 16 bits of grey
uint crcPng(in string chunkName, in ubyte[] buf){
uint crcPng(in char[] chunkName, in ubyte[] buf){
uint c = update_crc(0xffffffffL, cast(ubyte[]) chunkName);
return update_crc(c, buf) ^ 0xffffffffL;
}

View File

@ -2024,6 +2024,16 @@ unittest {
});
}
unittest {
// ternary precedence
interpret(q{
assert(0 == 0 ? true : false == true);
assert((0 == 0) ? true : false == true);
// lol FIXME
//assert(((0 == 0) ? true : false) == true);
});
}
class ForeachExpression : Expression {
VariableDeclaration decl;
Expression subject;

View File

@ -2972,7 +2972,7 @@ struct EventLoopImpl {
else
void initialize(long pulseTimeout) {
version(Windows) {
if(pulseTimeout)
if(pulseTimeout && handlePulse !is null)
pulser = new Timer(cast(int) pulseTimeout, handlePulse);
if (customEventH is null) {
@ -3003,7 +3003,7 @@ struct EventLoopImpl {
displayFd = display.fd;
}
if(pulseTimeout) {
if(pulseTimeout && handlePulse !is null) {
pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
if(pulseFd == -1)
throw new Exception("pulse timer create failed");
@ -14431,6 +14431,7 @@ void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
glBufferData(target, data.length, data.ptr, usage);
}
/+
/++
A matrix for simple uses that easily integrates with [OpenGlShader].
@ -14477,6 +14478,7 @@ struct BasicMatrix(int columns, int rows, T = float) {
return BasicMatrix.init;
}
}
+/
/++
Convenience class for using opengl shaders.

View File

@ -1,5 +1,31 @@
/++
This provides a kind of web template support, built on top of [arsd.dom] and [arsd.script], in support of [arsd.cgi].
```html
<main>
<%=HTML some_var_with_html %>
<%= some_var %>
<if-true cond="whatever">
whatever == true
</if-true>
<or-else>
whatever == false
</or-else>
<for-each over="some_array" as="item">
<%= item %>
</for-each>
<or-else>
there were no items.
</or-else>
<render-template file="partial.html" />
</main>
```
Functions available:
`encodeURIComponent`, `formatDate`, `dayOfWeek`, `formatTime`
+/
module arsd.webtemplate;
@ -10,12 +36,6 @@ import arsd.dom;
public import arsd.jsvar : var;
struct RenderTemplate {
string name;
var context = var.emptyObject;
var skeletonContext = var.emptyObject;
}
class TemplateException : Exception {
string templateName;
var context;
@ -103,6 +123,7 @@ Document renderTemplate(string templateName, var context = var.emptyObject, var
return skeleton;
} catch(Exception e) {
throw new TemplateException(templateName, context, e);
//throw e;
}
}
@ -145,7 +166,7 @@ void expandTemplate(Element root, var context) {
if(ele.tagName == "if-true") {
auto fragment = new DocumentFragment(null);
import arsd.script;
auto got = interpret(ele.attrs.cond, context).get!bool;
auto got = interpret(ele.attrs.cond, context).opCast!bool;
if(got) {
ele.tagName = "root";
expandTemplate(ele, context);
@ -256,9 +277,85 @@ immutable daysOfWeekFullNames = [
"Saturday"
];
/++
UDA to put on a method when using [WebPresenterWithTemplateSupport]. Overrides default generic element formatting and instead uses the specified template name to render the return value.
/+
mixin template WebTemplatePresenterSupport() {
}
Inside the template, the value returned by the function will be available in the context as the variable `data`.
+/
struct Template {
string name;
}
/++
UDA to put on a method when using [WebPresenterWithTemplateSupport]. Overrides the default template skeleton file name.
+/
struct Skeleton {
string name;
}
/++
Can be used as a return value from one of your own methods when rendering websites with [WebPresenterWithTemplateSupport].
+/
struct RenderTemplate {
string name;
var context = var.emptyObject;
var skeletonContext = var.emptyObject;
}
/++
Make a class that inherits from this with your further customizations, or minimally:
---
class MyPresenter : WebPresenterWithTemplateSupport!MyPresenter { }
---
+/
template WebPresenterWithTemplateSupport(CTRP) {
import arsd.cgi;
class WebPresenterWithTemplateSupport : WebPresenter!(CTRP) {
override Element htmlContainer() {
auto skeleton = renderTemplate("generic.html");
return skeleton.requireSelector("main");
}
static struct Meta {
typeof(null) at;
string templateName;
string skeletonName;
alias at this;
}
template methodMeta(alias method) {
static Meta helper() {
Meta ret;
// ret.at = typeof(super).methodMeta!method;
foreach(attr; __traits(getAttributes, method))
static if(is(typeof(attr) == Template))
ret.templateName = attr.name;
else static if(is(typeof(attr) == Skeleton))
ret.skeletonName = attr.name;
return ret;
}
enum methodMeta = helper();
}
/// You can override this
void addContext(Cgi cgi, var ctx) {}
void presentSuccessfulReturnAsHtml(T : RenderTemplate)(Cgi cgi, T ret, Meta meta) {
addContext(cgi, ret.context);
auto skeleton = renderTemplate(ret.name, ret.context, ret.skeletonContext);
cgi.setResponseContentType("text/html; charset=utf8");
cgi.gzipResponse = true;
cgi.write(skeleton.toString(), true);
}
void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, Meta meta) {
if(meta.templateName.length) {
var obj = var.emptyObject;
obj.data = ret;
presentSuccessfulReturnAsHtml(cgi, RenderTemplate(meta.templateName, obj), meta);
} else
super.presentSuccessfulReturnAsHtml(cgi, ret, meta);
}
}
}