mirror of https://github.com/adamdruppe/arsd.git
Merge branch 'master' of github.com:adamdruppe/arsd
This commit is contained in:
commit
427b325296
97
cgi.d
97
cgi.d
|
@ -1107,7 +1107,6 @@ class Cgi {
|
|||
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
void writeToFile(string filenameToSaveTo) const {
|
||||
import std.file;
|
||||
|
@ -5272,7 +5271,7 @@ ScheduledJobHelper schedule(alias fn, T...)(T args) {
|
|||
|
||||
///
|
||||
interface ScheduledJobServer {
|
||||
///
|
||||
/// Use the [schedule] function for a higher-level interface.
|
||||
int scheduleJob(int whenIs, int when, string executable, string func, string[] args);
|
||||
///
|
||||
void cancelJob(int jobId);
|
||||
|
@ -5282,6 +5281,16 @@ class ScheduledJobServerConnection : ScheduledJobServer {
|
|||
mixin ImplementRpcClientInterface!(ScheduledJobServer, "/tmp/arsd_scheduled_job_server");
|
||||
}
|
||||
|
||||
final class ScheduledJobServerImplementation : ScheduledJobServer {
|
||||
protected int scheduleJob(int whenIs, int when, string executable, string func, string[] args) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void cancelJob(int jobId) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
interface EventSourceServer {
|
||||
/++
|
||||
|
@ -6044,6 +6053,18 @@ static auto elementFor(T)(string displayName, string name) {
|
|||
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));
|
||||
|
@ -6370,6 +6391,8 @@ auto formatReturnValueAsHtml(T)(T t) {
|
|||
}
|
||||
|
||||
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
|
||||
|
@ -6439,18 +6462,6 @@ auto formatReturnValueAsHtml(T)(T t) {
|
|||
FIXME
|
||||
+/
|
||||
class WebPresenter() {
|
||||
|
||||
}
|
||||
|
||||
/++
|
||||
The base class for the [dispatcher] function and object support.
|
||||
+/
|
||||
class WebObject(Helper = void) {
|
||||
Cgi cgi;
|
||||
void initialize(Cgi cgi) {
|
||||
this.cgi = cgi;
|
||||
}
|
||||
|
||||
string script() {
|
||||
return `
|
||||
`;
|
||||
|
@ -6569,6 +6580,19 @@ class WebObject(Helper = void) {
|
|||
}
|
||||
}
|
||||
|
||||
/++
|
||||
The base class for the [dispatcher] function and object support.
|
||||
+/
|
||||
class WebObject(Helper = void) {
|
||||
Cgi cgi;
|
||||
WebPresenter!() presenter;
|
||||
|
||||
void initialize(Cgi cgi, WebPresenter!() presenter) {
|
||||
this.cgi = cgi;
|
||||
this.presenter = presenter;
|
||||
}
|
||||
}
|
||||
|
||||
/++
|
||||
Serves a class' methods, as a kind of low-state RPC over the web. To be used with [dispatcher].
|
||||
|
||||
|
@ -6582,10 +6606,10 @@ auto serveApi(T)(string urlPrefix) {
|
|||
import arsd.dom;
|
||||
import arsd.jsvar;
|
||||
|
||||
static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) {
|
||||
static bool handler(string urlPrefix, Cgi cgi, WebPresenter!() presenter, DispatcherDetails details) {
|
||||
|
||||
auto obj = new T();
|
||||
obj.initialize(cgi);
|
||||
obj.initialize(cgi, presenter);
|
||||
|
||||
switch(cgi.pathInfo[urlPrefix.length .. $]) {
|
||||
static foreach(methodName; __traits(derivedMembers, T)){{
|
||||
|
@ -6594,7 +6618,7 @@ auto serveApi(T)(string urlPrefix) {
|
|||
case urlify(methodName):
|
||||
switch(cgi.request("format", "html")) {
|
||||
case "html":
|
||||
auto container = obj.htmlContainer();
|
||||
auto container = obj.presenter.htmlContainer();
|
||||
try {
|
||||
auto ret = callFromCgi!(__traits(getMember, obj, methodName))(&__traits(getMember, obj, methodName), cgi);
|
||||
container.appendChild(formatReturnValueAsHtml(ret));
|
||||
|
@ -6623,12 +6647,12 @@ auto serveApi(T)(string urlPrefix) {
|
|||
case "script.js":
|
||||
cgi.setResponseContentType("text/javascript");
|
||||
cgi.gzipResponse = true;
|
||||
cgi.write(obj.script(), true);
|
||||
cgi.write(obj.presenter.script(), true);
|
||||
return true;
|
||||
case "style.css":
|
||||
cgi.setResponseContentType("text/css");
|
||||
cgi.gzipResponse = true;
|
||||
cgi.write(obj.style(), true);
|
||||
cgi.write(obj.presenter.style(), true);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -6768,6 +6792,9 @@ mixin template Presenter() {
|
|||
|
||||
}
|
||||
|
||||
// FIXME XSRF token, prolly can just put in a cookie and then it needs to be copied to header or form hidden value
|
||||
// https://use-the-index-luke.com/sql/partial-results/fetch-next-page
|
||||
|
||||
/++
|
||||
Base class for REST collections.
|
||||
+/
|
||||
|
@ -6934,7 +6961,7 @@ class CollectionOf(Obj, Helper = void) : RestObject!(Helper) {
|
|||
+/
|
||||
auto serveRestObject(T)(string urlPrefix) {
|
||||
assert(urlPrefix[$ - 1] != '/', "Do NOT use a trailing slash on REST objects.");
|
||||
static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) {
|
||||
static bool handler(string urlPrefix, Cgi cgi, WebPresenter!() presenter, DispatcherDetails details) {
|
||||
string url = cgi.pathInfo[urlPrefix.length .. $];
|
||||
|
||||
if(url.length && url[$ - 1] == '/') {
|
||||
|
@ -6943,7 +6970,7 @@ auto serveRestObject(T)(string urlPrefix) {
|
|||
return true;
|
||||
}
|
||||
|
||||
return restObjectServeHandler!T(cgi, url);
|
||||
return restObjectServeHandler!T(cgi, presenter, url);
|
||||
|
||||
}
|
||||
return DispatcherDefinition!handler(urlPrefix, false);
|
||||
|
@ -6957,7 +6984,7 @@ auto serveRestCollectionOf(T)(string urlPrefix) {
|
|||
return serveRestObject!(mixin(T.stringof ~ "s"))(urlPrefix);
|
||||
}
|
||||
|
||||
bool restObjectServeHandler(T)(Cgi cgi, string url) {
|
||||
bool restObjectServeHandler(T)(Cgi cgi, WebPresenter!() presenter, string url) {
|
||||
string urlId = null;
|
||||
if(url.length && url[0] == '/') {
|
||||
// asking for a subobject
|
||||
|
@ -6974,7 +7001,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
|
|||
|
||||
static if(is(T : CollectionOf!(C, P), C, P)) {
|
||||
if(urlId !is null) {
|
||||
return restObjectServeHandler!C(cgi, url); // FIXME? urlId);
|
||||
return restObjectServeHandler!C(cgi, presenter, url); // FIXME? urlId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7007,7 +7034,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
|
|||
// FIXME
|
||||
return ValidationResult.valid;
|
||||
};
|
||||
obj.initialize(cgi);
|
||||
obj.initialize(cgi, presenter);
|
||||
// FIXME: populate reflection info delegates
|
||||
|
||||
|
||||
|
@ -7016,12 +7043,12 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
|
|||
case "script.js":
|
||||
cgi.setResponseContentType("text/javascript");
|
||||
cgi.gzipResponse = true;
|
||||
cgi.write(obj.script(), true);
|
||||
cgi.write(obj.presenter.script(), true);
|
||||
return true;
|
||||
case "style.css":
|
||||
cgi.setResponseContentType("text/css");
|
||||
cgi.gzipResponse = true;
|
||||
cgi.write(obj.style(), true);
|
||||
cgi.write(obj.presenter.style(), true);
|
||||
return true;
|
||||
default:
|
||||
// intentionally blank
|
||||
|
@ -7047,7 +7074,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
|
|||
cgi.setResponseContentType("application/json");
|
||||
cgi.write(obj.toJson().toString, true);
|
||||
} else {
|
||||
auto container = obj.htmlContainer();
|
||||
auto container = obj.presenter.htmlContainer();
|
||||
if(addFormLinks) {
|
||||
static if(is(T : CollectionOf!(C, P), C, P))
|
||||
container.appendHtml(`
|
||||
|
@ -7083,7 +7110,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
|
|||
static if(is(T : CollectionOf!(C, P), C, P)) {
|
||||
auto results = obj.index();
|
||||
if(cgi.request("format", "html") == "html") {
|
||||
auto container = obj.htmlContainer();
|
||||
auto container = obj.presenter.htmlContainer();
|
||||
auto html = formatReturnValueAsHtml(results.results);
|
||||
container.appendHtml(`
|
||||
<form>
|
||||
|
@ -7119,7 +7146,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
|
|||
case "PUT":
|
||||
case "POST":
|
||||
// an editing form for the object
|
||||
auto container = obj.htmlContainer();
|
||||
auto container = obj.presenter.htmlContainer();
|
||||
static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) {
|
||||
auto form = (cgi.request("_method") == "POST") ? createAutomaticFormForObject(new obj.PostProxy()) : createAutomaticFormForObject(obj);
|
||||
} else {
|
||||
|
@ -7132,7 +7159,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
|
|||
break;
|
||||
case "DELETE":
|
||||
// FIXME: a delete form for the object (can be phrased "are you sure?")
|
||||
auto container = obj.htmlContainer();
|
||||
auto container = obj.presenter.htmlContainer();
|
||||
container.appendHtml(`
|
||||
<form method="POST">
|
||||
Are you sure you want to delete this item?
|
||||
|
@ -7237,7 +7264,7 @@ auto serveStaticFile(string urlPrefix, string filename = null, string contentTyp
|
|||
contentType = "application/javascript";
|
||||
}
|
||||
|
||||
static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) {
|
||||
static bool handler(string urlPrefix, Cgi cgi, WebPresenter!() presenter, DispatcherDetails details) {
|
||||
if(details.contentType.indexOf("image/") == 0)
|
||||
cgi.setCache(true);
|
||||
cgi.setResponseContentType(details.contentType);
|
||||
|
@ -7271,17 +7298,19 @@ auto serveStaticData(string urlPrefix, const(void)[] data, string contentType) {
|
|||
)) return;
|
||||
---
|
||||
+/
|
||||
bool dispatcher(definitions...)(Cgi cgi) {
|
||||
bool dispatcher(definitions...)(Cgi cgi, WebPresenter!() presenter = null) {
|
||||
if(presenter is null)
|
||||
presenter = new WebPresenter!();
|
||||
// I can prolly make this more efficient later but meh.
|
||||
static foreach(definition; definitions) {
|
||||
if(definition.rejectFurther) {
|
||||
if(cgi.pathInfo == definition.urlPrefix) {
|
||||
auto ret = definition.handler(definition.urlPrefix, cgi, definition.details);
|
||||
auto ret = definition.handler(definition.urlPrefix, cgi, presenter, definition.details);
|
||||
if(ret)
|
||||
return true;
|
||||
}
|
||||
} else if(cgi.pathInfo.startsWith(definition.urlPrefix)) {
|
||||
auto ret = definition.handler(definition.urlPrefix, cgi, definition.details);
|
||||
auto ret = definition.handler(definition.urlPrefix, cgi, presenter, definition.details);
|
||||
if(ret)
|
||||
return true;
|
||||
}
|
||||
|
|
3
dom.d
3
dom.d
|
@ -3,6 +3,9 @@
|
|||
// FIXME: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
|
||||
// FIXME: appendChild should not fail if the thing already has a parent; it should just automatically remove it per standard.
|
||||
|
||||
|
||||
// xml entity references?!
|
||||
|
||||
/++
|
||||
This is an html DOM implementation, started with cloning
|
||||
what the browser offers in Javascript, but going well beyond
|
||||
|
|
31
email.d
31
email.d
|
@ -26,6 +26,13 @@ struct MimeAttachment {
|
|||
string id; ///
|
||||
}
|
||||
|
||||
///
|
||||
enum ToType {
|
||||
to,
|
||||
cc,
|
||||
bcc
|
||||
}
|
||||
|
||||
|
||||
/++
|
||||
For OUTGOING email
|
||||
|
@ -65,12 +72,32 @@ class EmailMessage {
|
|||
private bool isMime = false;
|
||||
private bool isHtml = false;
|
||||
|
||||
///
|
||||
void addRecipient(string name, string email, ToType how = ToType.to) {
|
||||
addRecipient(`"`~name~`" <`~email~`>`, how);
|
||||
}
|
||||
|
||||
///
|
||||
void addRecipient(string who, ToType how = ToType.to) {
|
||||
final switch(how) {
|
||||
case ToType.to:
|
||||
to ~= who;
|
||||
break;
|
||||
case ToType.cc:
|
||||
cc ~= who;
|
||||
break;
|
||||
case ToType.bcc:
|
||||
bcc ~= who;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
void setTextBody(string text) {
|
||||
textBody = text;
|
||||
textBody = text.strip;
|
||||
}
|
||||
/// automatically sets a text fallback if you haven't already
|
||||
void setHtmlBody(string html) {
|
||||
void setHtmlBody()(string html) {
|
||||
isMime = true;
|
||||
isHtml = true;
|
||||
htmlBody = html;
|
||||
|
|
2
http2.d
2
http2.d
|
@ -1672,7 +1672,7 @@ class HttpApiClient() {
|
|||
///
|
||||
var throwOnError(HttpResponse res) {
|
||||
if(res.code < 200 || res.code >= 300)
|
||||
throw new Exception(res.codeText);
|
||||
throw new Exception(res.codeText ~ " " ~ res.contentText);
|
||||
|
||||
var response = var.fromJson(res.contentText);
|
||||
if(response.errors) {
|
||||
|
|
|
@ -8,6 +8,13 @@
|
|||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/Controls/about-custom-draw
|
||||
|
||||
// FIXME: make the scroll thing go to bottom when the content changes.
|
||||
|
||||
// add a knob slider view... you click and go up and down so basically same as a vertical slider, just presented as a round image
|
||||
|
||||
// FIXME: the scroll area MUST be fixed to use the proper apis under the hood.
|
||||
|
||||
|
||||
// FIXME: add a command search thingy built in and implement tip.
|
||||
// FIXME: omg omg what if menu functions have arguments and it can pop up a gui or command line script them?!
|
||||
|
||||
|
|
|
@ -142,6 +142,11 @@ $(SIDE_BY_SIDE
|
|||
)
|
||||
)
|
||||
|
||||
$(TIP
|
||||
If you are going to use the library with a SDL OpenGL context,
|
||||
try working with a backwards compatible context profile.
|
||||
)
|
||||
|
||||
|
||||
Creating drawing context
|
||||
========================
|
||||
|
|
2
script.d
2
script.d
|
@ -1976,6 +1976,8 @@ Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: unary ! doesn't work right
|
||||
|
||||
funcLoop: while(!tokens.empty) {
|
||||
auto peek = tokens.front;
|
||||
if(peek.type == ScriptToken.Type.symbol) {
|
||||
|
|
56
web.d
56
web.d
|
@ -2533,7 +2533,7 @@ class NoSuchFunctionException : NoSuchPageException {
|
|||
type fromUrlParam(type)(in string ofInterest, in string name, in string[][string] all) {
|
||||
type ret;
|
||||
|
||||
static if(isArray!(type) && !isSomeString!(type)) {
|
||||
static if(!is(type == enum) && isArray!(type) && !isSomeString!(type)) {
|
||||
// how do we get an array out of a simple string?
|
||||
// FIXME
|
||||
static assert(0);
|
||||
|
@ -2561,6 +2561,17 @@ type fromUrlParam(type)(in string ofInterest, in string name, in string[][string
|
|||
if(lol in all)
|
||||
ret.tupleof[idx] = fromUrlParam!(typeof(thing))(all[lol], lol, all);
|
||||
}
|
||||
} else static if(is(type == enum)) {
|
||||
sw: switch(ofInterest) {
|
||||
static foreach(N; __traits(allMembers, type)) {
|
||||
case N:
|
||||
ret = __traits(getMember, type, N);
|
||||
break sw;
|
||||
}
|
||||
default:
|
||||
throw new InvalidParameterException(name, ofInterest, "");
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
else static if(is(type : struct)) {
|
||||
|
@ -2568,7 +2579,6 @@ type fromUrlParam(type)(in string ofInterest, in string name, in string[][string
|
|||
}
|
||||
*/
|
||||
else {
|
||||
// enum should be handled by this too
|
||||
ret = to!type(ofInterest);
|
||||
} // FIXME: can we support classes?
|
||||
|
||||
|
@ -2580,7 +2590,7 @@ type fromUrlParam(type)(in string[] ofInterest, in string name, in string[][stri
|
|||
type ret;
|
||||
|
||||
// Arrays in a query string are sent as the name repeating...
|
||||
static if(isArray!(type) && !isSomeString!type) {
|
||||
static if(!is(type == enum) && isArray!(type) && !isSomeString!type) {
|
||||
foreach(a; ofInterest) {
|
||||
ret ~= fromUrlParam!(ElementType!(type))(a, name, all);
|
||||
}
|
||||
|
@ -2770,6 +2780,11 @@ string formatAs(T, R)(T ret, string format, R api = null, JSONValue* returnValue
|
|||
} else {
|
||||
format = "html";
|
||||
}
|
||||
|
||||
static if(is(typeof(ret) : K[], K)) {
|
||||
static if(is(K == struct))
|
||||
format = "table";
|
||||
}
|
||||
}
|
||||
|
||||
string retstr;
|
||||
|
@ -2814,9 +2829,15 @@ string formatAs(T, R)(T ret, string format, R api = null, JSONValue* returnValue
|
|||
void gotATable(Table table) {
|
||||
if(format == "csv") {
|
||||
retstr = tableToCsv(table);
|
||||
} else if(format == "table")
|
||||
retstr = table.toString();
|
||||
else assert(0);
|
||||
} else if(format == "table") {
|
||||
auto div = Element.make("div");
|
||||
if(api !is null) {
|
||||
auto cgi = api.cgi;
|
||||
div.addChild("a", "Download as CSV", cgi.pathInfo ~ "?" ~ cgi.queryString ~ "&format=csv&envelopeFormat=csv");
|
||||
}
|
||||
div.appendChild(table);
|
||||
retstr = div.toString();
|
||||
} else assert(0);
|
||||
|
||||
|
||||
if(returnValue !is null)
|
||||
|
@ -2837,10 +2858,13 @@ string formatAs(T, R)(T ret, string format, R api = null, JSONValue* returnValue
|
|||
} else static if(is(typeof(ret) : K[N][V], size_t N, K, V)) {
|
||||
auto table = cast(Table) Element.make("table");
|
||||
table.addClass("data-display");
|
||||
auto headerRow = table.addChild("tr");
|
||||
foreach(n; 0 .. N)
|
||||
table.addChild("th", "" ~ cast(char)(n + 'A'));
|
||||
foreach(k, v; ret) {
|
||||
auto row = table.addChild("tr");
|
||||
foreach(cell; v)
|
||||
table.addChild("td", to!string(cell));
|
||||
row.addChild("td", to!string(cell));
|
||||
}
|
||||
gotATable(table);
|
||||
break;
|
||||
|
@ -2936,6 +2960,9 @@ string toUrlName(string name) {
|
|||
string beautify(string name) {
|
||||
string n;
|
||||
|
||||
// really if this is cap and the following is lower, we want a space.
|
||||
// or in other words, if this is lower and previous is cap, we want a space injected before previous
|
||||
|
||||
// all caps names shouldn't get spaces
|
||||
if(name.length == 0 || name.toUpper() == name)
|
||||
return name;
|
||||
|
@ -2943,8 +2970,11 @@ string beautify(string name) {
|
|||
n ~= toUpper(name[0..1]);
|
||||
|
||||
dchar last;
|
||||
foreach(dchar c; name[1..$]) {
|
||||
if((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
|
||||
foreach(idx, dchar c; name[1..$]) {
|
||||
if(c >= 'A' && c <= 'Z') {
|
||||
if(idx + 1 < name[1 .. $].length && name[1 + idx + 1] >= 'a' && name[1 + idx + 1] <= 'z')
|
||||
n ~= " ";
|
||||
} else if(c >= '0' && c <= '9') {
|
||||
if(last != ' ')
|
||||
n ~= " ";
|
||||
}
|
||||
|
@ -3974,9 +4004,11 @@ Table structToTable(T)(Document document, T arr, string[] fieldsToSkip = null) i
|
|||
{
|
||||
auto thead = t.addChild("thead");
|
||||
auto tr = thead.addChild("tr");
|
||||
auto s = arr[0];
|
||||
foreach(idx, member; s.tupleof)
|
||||
tr.addChild("th", s.tupleof[idx].stringof[2..$]);
|
||||
if(arr.length) {
|
||||
auto s = arr[0];
|
||||
foreach(idx, member; s.tupleof)
|
||||
tr.addChild("th", s.tupleof[idx].stringof[2..$]);
|
||||
}
|
||||
}
|
||||
|
||||
bool odd = true;
|
||||
|
|
Loading…
Reference in New Issue