From ea88731d58838452c50c7c6f67c37c607dff02f8 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Mon, 7 Sep 2020 22:32:53 -0400 Subject: [PATCH] jawesome --- cgi.d | 131 ++++++++++++++++++++++++++++++++++++------------ color.d | 3 ++ curl.d | 2 +- htmlwidget.d | 57 ++++++++++----------- jsvar.d | 38 ++++++++++++-- png.d | 2 +- script.d | 10 ++++ simpledisplay.d | 6 ++- webtemplate.d | 119 +++++++++++++++++++++++++++++++++++++++---- 9 files changed, 289 insertions(+), 79 deletions(-) diff --git a/cgi.d b/cgi.d index 26b7377..583dcbc 100644 --- a/cgi.d +++ b/cgi.d @@ -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; diff --git a/color.d b/color.d index f5f2afe..db00690 100644 --- a/color.d +++ b/color.d @@ -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; diff --git a/curl.d b/curl.d index df3148d..74a3963 100644 --- a/curl.d +++ b/curl.d @@ -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; diff --git a/htmlwidget.d b/htmlwidget.d index 6ed6c05..c2d7e07 100644 --- a/htmlwidget.d +++ b/htmlwidget.d @@ -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; } diff --git a/jsvar.d b/jsvar.d index 6fbc8e1..d0e72fd 100644 --- a/jsvar.d +++ b/jsvar.d @@ -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); diff --git a/png.d b/png.d index f9c3d00..999e5b5 100644 --- a/png.d +++ b/png.d @@ -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; } diff --git a/script.d b/script.d index 4a6b00e..1413b1d 100644 --- a/script.d +++ b/script.d @@ -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; diff --git a/simpledisplay.d b/simpledisplay.d index 03a3021..7697e02 100644 --- a/simpledisplay.d +++ b/simpledisplay.d @@ -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. diff --git a/webtemplate.d b/webtemplate.d index 6021523..4fc1272 100644 --- a/webtemplate.d +++ b/webtemplate.d @@ -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 +
+ <%=HTML some_var_with_html %> + <%= some_var %> + + + whatever == true + + + whatever == false + + + + <%= item %> + + + there were no items. + + + +
+ ``` + + 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); + } + } +}