im the worst at greys

This commit is contained in:
Adam D. Ruppe 2019-07-06 13:22:25 -04:00
parent ba0ea0c717
commit b7ad04afd9
10 changed files with 1342 additions and 197 deletions

1007
cgi.d

File diff suppressed because it is too large Load Diff

View File

@ -238,6 +238,9 @@ class SelectBuilder : SqlBuilder {
Variant[string] vars;
void setVariable(T)(string name, T value) {
assert(name.length);
if(name[0] == '?')
name = name[1 .. $];
vars[name] = Variant(value);
}

View File

@ -6,6 +6,10 @@
module arsd.database_generation;
/*
FIXME: support partial indexes and maybe "using"
FIXME: support views
Let's put indexes in there too and make index functions be the preferred way of doing a query
by making them convenient af.
*/
@ -22,7 +26,9 @@ private enum UDA;
@UDA struct Unique { }
@UDA struct ForeignKey(alias toWhat, string behavior) {}
@UDA struct ForeignKey(alias toWhat, string behavior) {
alias ReferencedTable = __traits(parent, toWhat);
}
enum CASCADE = "ON UPDATE CASCADE ON DELETE CASCADE";
enum NULLIFY = "ON UPDATE CASCADE ON DELETE SET NULL";
@ -94,12 +100,12 @@ string generateCreateTableFor(alias O)() {
outputted = true;
} else static if(is(typeof(member) == Index!Fields, Fields...)) {
string fields = "";
foreach(field; Fields) {
static foreach(field; Fields) {
if(fields.length)
fields ~= ", ";
fields ~= __traits(identifier, field);
}
addAfterTableSql("CREATE INDEX " ~ memberName ~ " ON " ~ tableName ~ "("~fields~")");
addAfterTableSql("CREATE INDEX " ~ tableName ~ "_" ~ memberName ~ " ON " ~ tableName ~ "("~fields~")");
} else static if(is(typeof(member) == UniqueIndex!Fields, Fields...)) {
string fields = "";
static foreach(field; Fields) {
@ -107,7 +113,7 @@ string generateCreateTableFor(alias O)() {
fields ~= ", ";
fields ~= __traits(identifier, field);
}
addAfterTableSql("CREATE UNIQUE INDEX " ~ memberName ~ " ON " ~ tableName ~ "("~fields~")");
addAfterTableSql("CREATE UNIQUE INDEX " ~ tableName ~ "_" ~ memberName ~ " ON " ~ tableName ~ "("~fields~")");
} else static if(is(typeof(member) T)) {
if(outputted) {
sql ~= ",";
@ -237,7 +243,17 @@ private string beautify(string name, char space = ' ', bool allLowerCase = false
}
import arsd.database;
/++
+/
void save(O)(ref O t, Database db) {
t.insert(db);
}
/++
+/
void insert(O)(ref O t, Database db) {
auto builder = new InsertBuilder;
builder.setTable(toTableName(O.stringof));
@ -253,9 +269,14 @@ void save(O)(ref O t, Database db) {
builder.addVariable(memberName, v.value);
} else static if(is(T == int))
builder.addVariable(memberName, __traits(getMember, t, memberName));
else static if(is(T == Serial))
{} // skip, let it auto-fill
else static if(is(T == string))
else static if(is(T == Serial)) {
auto v = __traits(getMember, t, memberName).value;
if(v) {
builder.addVariable(memberName, v);
} else {
// skip and let it auto-fill
}
} else static if(is(T == string))
builder.addVariable(memberName, __traits(getMember, t, memberName));
else static if(is(T == double))
builder.addVariable(memberName, __traits(getMember, t, memberName));
@ -273,44 +294,42 @@ void save(O)(ref O t, Database db) {
t.id.value = to!int(row[0]);
}
///
class RecordNotFoundException : Exception {
this() { super("RecordNotFoundException"); }
}
/++
Returns a given struct populated from the database. Assumes types known to this module.
MyItem item = db.find!(MyItem.id)(3);
If you just give a type, it assumes the relevant index is "id".
+/
T find(T)(Database db, int id) {
import std.conv;
auto find(alias T)(Database db, int id) {
// FIXME: if T is an index, search by it.
// if it is unique, return an individual item.
// if not, return the array
foreach(record; db.query("SELECT * FROM " ~ toTableName(T.stringof) ~ " WHERE id = ?", id)) {
T t;
T t;
populateFromDbRow(t, record);
return t;
// if there is ever a second record, that's a wtf, but meh.
}
throw new RecordNotFoundException();
}
private void populateFromDbRow(T)(ref T t, Row record) {
foreach(field, value; record) {
sw: switch(field) {
static foreach(memberName; __traits(allMembers, T)) {
case memberName:
static if(is(typeof(__traits(getMember, T, memberName)))) {
typeof(__traits(getMember, t, memberName)) val;
alias V = typeof(val);
static if(is(V == Constraint!constraintSql, string constraintSql)) {
} else static if(is(V == Nullable!P, P)) {
// FIXME
if(value.length) {
val.isNull = false;
val.value = to!P(value);
}
} else static if(is(V == int) || is(V == string) || is(V == bool) || is(V == double)) {
val = to!V(value);
} else static if(is(V == enum)) {
val = cast(V) to!int(value);
} else static if(is(T == Timestamp)) {
// FIXME
} else static if(is(V == Serial)) {
val.value = to!int(value);
}
__traits(getMember, t, memberName) = val;
populateFromDbVal(__traits(getMember, t, memberName), value);
}
break sw;
}
@ -318,8 +337,240 @@ T find(T)(Database db, int id) {
// intentionally blank
}
}
return t;
// if there is ever a second record, that's a wtf, but meh.
}
throw new RecordNotFoundException();
}
private void populateFromDbVal(V)(ref V val, string value) {
import std.conv;
static if(is(V == Constraint!constraintSql, string constraintSql)) {
} else static if(is(V == Nullable!P, P)) {
// FIXME
if(value.length) {
val.isNull = false;
val.value = to!P(value);
}
} else static if(is(V == bool)) {
val = value == "true" || value == "1";
} else static if(is(V == int) || is(V == string) || is(V == double)) {
val = to!V(value);
} else static if(is(V == enum)) {
val = cast(V) to!int(value);
} else static if(is(T == Timestamp)) {
// FIXME
} else static if(is(V == Serial)) {
val.value = to!int(value);
}
}
/++
Gets all the children of that type. Specifically, it looks in T for a ForeignKey referencing B and queries on that.
To do a join through a many-to-many relationship, you could get the children of the join table, then get the children of that...
Or better yet, use real sql. This is more intended to get info where there is one parent row and then many child
rows, not for a combined thing.
+/
QueryBuilderHelper!(T[]) children(T, B)(B base) {
int countOfAssociations() {
int count = 0;
static foreach(memberName; __traits(allMembers, T))
static foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {{
static if(is(attr == ForeignKey!(K, policy), alias K, string policy)) {
static if(is(attr.ReferencedTable == B))
count++;
}
}}
return count;
}
static assert(countOfAssociations() == 1, T.stringof ~ " does not have exactly one foreign key of type " ~ B.stringof);
string keyName() {
static foreach(memberName; __traits(allMembers, T))
static foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {{
static if(is(attr == ForeignKey!(K, policy), alias K, string policy)) {
static if(is(attr.ReferencedTable == B))
return memberName;
}
}}
}
return QueryBuilderHelper!(T[])(toTableName(T.stringof)).where!(mixin(keyName ~ " => base.id"));
}
/++
Finds the single row associated with a foreign key in `base`.
`T` is used to find the key, unless ambiguous, in which case you must pass `key`.
To do a join through a many-to-many relationship, go to [children] or use real sql.
+/
T associated(B, T, string key = null)(B base, Database db) {
int countOfAssociations() {
int count = 0;
static foreach(memberName; __traits(allMembers, B))
static foreach(attr; __traits(getAttributes, __traits(getMember, B, memberName))) {
static if(is(attr == ForeignKey!(K, policy), alias K, string policy)) {
static if(is(attr.ReferencedTable == T))
static if(key is null || key == memberName)
count++;
}
}
return count;
}
static if(key is null) {
enum coa = countOfAssociations();
static assert(coa != 0, B.stringof ~ " has no association of type " ~ T);
static assert(coa == 1, B.stringof ~ " has multiple associations of type " ~ T ~ "; please specify the key you want");
static foreach(memberName; __traits(allMembers, B))
static foreach(attr; __traits(getAttributes, __traits(getMember, B, memberName))) {
static if(is(attr == ForeignKey!(K, policy), alias K, string policy)) {
static if(is(attr.ReferencedTable == T))
return db.find!T(__traits(getMember, base, memberName));
}
}
} else {
static assert(countOfAssociations() == 1, B.stringof ~ " does not have a key named " ~ key ~ " of type " ~ T);
static foreach(attr; __traits(getAttributes, __traits(getMember, B, memberName))) {
static if(is(attr == ForeignKey!(K, policy), alias K, string policy)) {
static if(is(attr.ReferencedTable == T)) {
return db.find!T(__traits(getMember, base, key));
}
}
}
assert(0);
}
}
/++
It will return an aggregate row with a member of type of each table in the join.
Could do an anonymous object for other things in the sql...
+/
auto join(TableA, TableB, ThroughTable = void)() {}
/++
+/
struct QueryBuilderHelper(T) {
static if(is(T == R[], R))
alias TType = R;
else
alias TType = T;
SelectBuilder selectBuilder;
this(string tableName) {
selectBuilder = new SelectBuilder();
selectBuilder.table = tableName;
selectBuilder.fields = ["*"];
}
T execute(Database db) {
selectBuilder.db = db;
static if(is(T == R[], R)) {
} else {
selectBuilder.limit = 1;
}
T ret;
bool first = true;
foreach(row; db.query(selectBuilder.toString())) {
TType t;
populateFromDbRow(t, row);
static if(is(T == R[], R))
ret ~= t;
else {
if(first) {
ret = t;
first = false;
} else {
assert(0);
}
}
}
return ret;
}
///
typeof(this) orderBy(string criterion)() {
string name() {
int idx = 0;
while(idx < criterion.length && criterion[idx] != ' ')
idx++;
return criterion[0 .. idx];
}
string direction() {
int idx = 0;
while(idx < criterion.length && criterion[idx] != ' ')
idx++;
import std.string;
return criterion[idx .. $].strip;
}
static assert(is(typeof(__traits(getMember, TType, name()))), TType.stringof ~ " has no field " ~ name());
static assert(direction().length == 0 || direction() == "ASC" || direction() == "DESC", "sort direction must be empty, ASC, or DESC");
selectBuilder.orderBys ~= criterion;
return this;
}
}
QueryBuilderHelper!(T[]) from(T)() {
return QueryBuilderHelper!(T[])(toTableName(T.stringof));
}
/// ditto
template where(conditions...) {
Qbh where(Qbh)(Qbh this_, string[] sqlCondition...) {
assert(this_.selectBuilder !is null);
static string extractName(string s) {
if(s.length == 0) assert(0);
auto i = s.length - 1;
while(i) {
if(s[i] == ')') {
// got to close paren, now backward to non-identifier char to get name
auto end = i;
while(i) {
if(s[i] == ' ')
return s[i + 1 .. end];
i--;
}
assert(0);
}
i--;
}
assert(0);
}
static foreach(idx, cond; conditions) {{
// I hate this but __parameters doesn't work here for some reason
// see my old thread: https://forum.dlang.org/post/awjuoemsnmxbfgzhgkgx@forum.dlang.org
enum name = extractName(typeof(cond!int).stringof);
auto value = cond(null);
// FIXME: convert the value as necessary
static if(is(typeof(value) == Serial))
auto dbvalue = value.value;
else
auto dbvalue = value;
import std.conv;
auto placeholder = "?_internal" ~ to!string(idx);
this_.selectBuilder.wheres ~= name ~ " = " ~ placeholder;
this_.selectBuilder.setVariable(placeholder, dbvalue);
static assert(is(typeof(__traits(getMember, Qbh.TType, name))), Qbh.TType.stringof ~ " has no member " ~ name);
static if(is(typeof(__traits(getMember, Qbh.TType, name)) == int))
static assert(is(typeof(value) == int) || is(typeof(value) == Serial), Qbh.TType.stringof ~ " is a integer key, but you passed an incompatible " ~ typeof(value));
else
static assert(is(typeof(__traits(getMember, Qbh.TType, name)) == typeof(value)), Qbh.TType.stringof ~ "." ~ name ~ " is not of type " ~ typeof(value).stringof);
}}
this_.selectBuilder.wheres ~= sqlCondition;
return this_;
}
}

75
dom.d
View File

@ -3,6 +3,8 @@
// 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.
// FIXME: the scriptable list is quite arbitrary
// xml entity references?!
@ -39,7 +41,7 @@ module arsd.dom;
version(with_arsd_jsvar)
import arsd.jsvar;
else {
enum Scriptable;
enum scriptable = "arsd_jsvar_compatible";
}
// this is only meant to be used at compile time, as a filter for opDispatch
@ -1457,6 +1459,7 @@ class Element {
}
/// Adds a string to the class attribute. The class attribute is used a lot in CSS.
@scriptable
Element addClass(string c) {
if(hasClass(c))
return this; // don't add it twice
@ -1473,6 +1476,7 @@ class Element {
}
/// Removes a particular class name.
@scriptable
Element removeClass(string c) {
if(!hasClass(c))
return this;
@ -2162,6 +2166,7 @@ class Element {
/// Note: you can give multiple selectors, separated by commas.
/// It will return the first match it finds.
@scriptable
Element querySelector(string selector) {
// FIXME: inefficient; it gets all results just to discard most of them
auto list = getElementsBySelector(selector);
@ -2258,6 +2263,7 @@ class Element {
Note that the returned string is decoded, so it no longer contains any xml entities.
*/
@scriptable
string getAttribute(string name) const {
if(parentDocument && parentDocument.loose)
name = name.toLower();
@ -2271,6 +2277,7 @@ class Element {
/**
Sets an attribute. Returns this for easy chaining
*/
@scriptable
Element setAttribute(string name, string value) {
if(parentDocument && parentDocument.loose)
name = name.toLower();
@ -2295,6 +2302,7 @@ class Element {
/**
Returns if the attribute exists.
*/
@scriptable
bool hasAttribute(string name) {
if(parentDocument && parentDocument.loose)
name = name.toLower();
@ -2308,6 +2316,7 @@ class Element {
/**
Removes the given attribute from the element.
*/
@scriptable
Element removeAttribute(string name)
out(ret) {
assert(ret is this);
@ -2657,6 +2666,7 @@ class Element {
See_Also:
[firstInnerText], [directText], [innerText], [appendChild]
+/
@scriptable
Element appendText(string text) {
Element e = new TextNode(parentDocument, text);
appendChild(e);
@ -2686,6 +2696,7 @@ class Element {
This is similar to `element.innerHTML += "html string";` in Javascript.
+/
@scriptable
Element[] appendHtml(string html) {
Document d = new Document("<root>" ~ html ~ "</root>");
return stealChildren(d.root);
@ -3028,6 +3039,7 @@ class Element {
It is more like textContent.
*/
@scriptable
@property string innerText() const {
string s;
foreach(child; children) {
@ -3039,10 +3051,14 @@ class Element {
return s;
}
///
alias textContent = innerText;
/**
Sets the inside text, replacing all children. You don't
have to worry about entity encoding.
*/
@scriptable
@property void innerText(string text) {
selfClosed = false;
Element e = new TextNode(parentDocument, text);
@ -3069,7 +3085,7 @@ class Element {
Miscellaneous
*********************************/
/// This is a full clone of the element
/// This is a full clone of the element. Alias for cloneNode(true) now. Don't extend it.
@property Element cloned()
/+
out(ret) {
@ -3080,27 +3096,25 @@ class Element {
body {
+/
{
auto e = Element.make(this.tagName);
e.parentDocument = this.parentDocument;
e.attributes = this.attributes.aadup;
e.selfClosed = this.selfClosed;
foreach(child; children) {
e.appendChild(child.cloned);
}
return e;
return this.cloneNode(true);
}
/// Clones the node. If deepClone is true, clone all inner tags too. If false, only do this tag (and its attributes), but it will have no contents.
Element cloneNode(bool deepClone) {
if(deepClone)
return this.cloned;
// shallow clone
auto e = Element.make(this.tagName);
e.parentDocument = this.parentDocument;
e.attributes = this.attributes.aadup;
e.selfClosed = this.selfClosed;
if(deepClone) {
foreach(child; children) {
e.appendChild(child.cloneNode(true));
}
}
return e;
return e;
}
@ -4214,6 +4228,11 @@ class RawSource : SpecialElement {
return source;
}
override RawSource cloneNode(bool deep) {
return new RawSource(parentDocument, source);
}
///.
string source;
}
@ -4253,6 +4272,10 @@ class PhpCode : ServerSideCode {
super(_parentDocument, "php");
source = s;
}
override PhpCode cloneNode(bool deep) {
return new PhpCode(parentDocument, source);
}
}
///.
@ -4262,6 +4285,10 @@ class AspCode : ServerSideCode {
super(_parentDocument, "asp");
source = s;
}
override AspCode cloneNode(bool deep) {
return new AspCode(parentDocument, source);
}
}
///.
@ -4278,6 +4305,10 @@ class BangInstruction : SpecialElement {
return this.source;
}
override BangInstruction cloneNode(bool deep) {
return new BangInstruction(parentDocument, source);
}
///.
override string writeToAppender(Appender!string where = appender!string()) const {
auto start = where.data.length;
@ -4308,6 +4339,10 @@ class QuestionInstruction : SpecialElement {
tagName = "#qpi";
}
override QuestionInstruction cloneNode(bool deep) {
return new QuestionInstruction(parentDocument, source);
}
///.
override string nodeValue() const {
return this.source;
@ -4344,6 +4379,10 @@ class HtmlComment : SpecialElement {
tagName = "#comment";
}
override HtmlComment cloneNode(bool deep) {
return new HtmlComment(parentDocument, source);
}
///.
override string nodeValue() const {
return this.source;
@ -4399,7 +4438,7 @@ class TextNode : Element {
}
///.
override @property Element cloned() {
override @property TextNode cloneNode(bool deep) {
auto n = new TextNode(parentDocument, contents);
return n;
}
@ -7171,11 +7210,11 @@ private bool isSimpleWhite(dchar c) {
}
/*
Copyright: Adam D. Ruppe, 2010 - 2017
Copyright: Adam D. Ruppe, 2010 - 2019
License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
Authors: Adam D. Ruppe, with contributions by Nick Sabalausky, Trass3r, and ketmar among others
Copyright Adam D. Ruppe 2010-2017.
Copyright Adam D. Ruppe 2010-2019.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)

View File

@ -1,4 +1,4 @@
// Copyright 2013-2017, Adam D. Ruppe.
// Copyright 2013-2019, Adam D. Ruppe.
/++
This is version 2 of my http/1.1 client implementation.

29
jsvar.d
View File

@ -666,8 +666,9 @@ struct var {
return var(val.toString());
};
}
} else
} else static if(is(typeof(__traits(getMember, t, member)))) {
this[member] = __traits(getMember, t, member);
}
}
} else {
// assoc array
@ -704,9 +705,11 @@ struct var {
public var opOpAssign(string op, T)(T t) {
if(payloadType() == Type.Object) {
var* operator = this._payload._object._peekMember("opOpAssign", true);
if(operator !is null && operator._type == Type.Function)
return operator.call(this, op, t);
if(this._payload._object !is null) {
var* operator = this._payload._object._peekMember("opOpAssign", true);
if(operator !is null && operator._type == Type.Function)
return operator.call(this, op, t);
}
}
return _op!(this, this, op, T)(t);
@ -811,6 +814,17 @@ struct var {
return this.get!string;
}
public T getWno(T)() {
if(payloadType == Type.Object) {
if(auto wno = cast(WrappedNativeObject) this._payload._object) {
auto no = cast(T) wno.getObject();
if(no !is null)
return no;
}
}
return null;
}
public T get(T)() if(!is(T == void)) {
static if(is(T == var)) {
return this;
@ -1219,11 +1233,12 @@ struct var {
public ref var opIndexAssign(T)(T t, size_t idx, string file = __FILE__, size_t line = __LINE__) {
if(_type == Type.Array) {
alias arr = this._payload._array;
if(idx >= this._payload._array.length)
this._payload._array.length = idx + 1;
this._payload._array[idx] = t;
return this._payload._array[idx];
} else if(_type == Type.Object) {
return opIndexAssign(t, to!string(idx), file, line);
}
version(jsvar_throw)
throw new DynamicTypeException(this, Type.Array, file, line);
@ -1764,6 +1779,10 @@ class WrappedOpaque(T) : PrototypeObject if(!isPointer!T && !is(T == class)) {
}
WrappedOpaque!Obj wrapOpaquely(Obj)(Obj obj) {
static if(is(Obj == class)) {
if(obj is null)
return null;
}
return new WrappedOpaque!Obj(obj);
}

2
png.d
View File

@ -171,7 +171,7 @@ void convertPngData(ubyte type, ubyte depth, const(ubyte)[] data, int width, uby
if(depth < 8) {
if(p == (1 << depth - 1)) {
p <<= 8 - depth;
p |= (1 << (1 - depth)) - 1;
p |= 1 << depth - 1;
} else {
p <<= 8 - depth;
}

View File

@ -347,7 +347,7 @@ private enum string[] symbols = [
"+=", "-=", "*=", "/=", "~=", "==", "<=", ">=","!=", "%=",
"&=", "|=", "^=",
"..",
".",",",";",":",
"?", ".",",",";",":",
"[", "]", "{", "}", "(", ")",
"&", "|", "^",
"+", "-", "*", "/", "=", "<", ">","~","!","%"
@ -1596,6 +1596,39 @@ class IfExpression : Expression {
}
}
class TernaryExpression : Expression {
Expression condition;
Expression ifTrue;
Expression ifFalse;
this() {}
override InterpretResult interpret(PrototypeObject sc) {
InterpretResult result;
assert(condition !is null);
auto ifScope = new PrototypeObject();
ifScope.prototype = sc;
if(condition.interpret(ifScope).value) {
result = ifTrue.interpret(ifScope);
} else {
result = ifFalse.interpret(ifScope);
}
return InterpretResult(result.value, sc, result.flowControl);
}
override string toString() {
string code = "";
code ~= condition.toString();
code ~= " ? ";
code ~= ifTrue.toString();
code ~= " : ";
code ~= ifFalse.toString();
return code;
}
}
// this is kinda like a placement new, and currently isn't exposed inside the language,
// but is used for class inheritance
class ShallowCopyExpression : Expression {
@ -1814,7 +1847,7 @@ ScriptToken requireNextToken(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Sc
throw new ScriptCompileException("script ended prematurely", 0, file, line);
auto next = tokens.front;
if(next.type != type || (str !is null && next.str != str))
throw new ScriptCompileException("unexpected '"~next.str~"'", next.lineNumber, file, line);
throw new ScriptCompileException("unexpected '"~next.str~"' while expecting " ~ to!string(type) ~ " " ~ str, next.lineNumber, file, line);
tokens.popFront();
return next;
@ -2086,6 +2119,7 @@ Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
case "}": // possible FIXME
case ",": // possible FIXME these are passed on to the next thing
case ";":
case ":": // idk
return e1;
case ".":
@ -2100,6 +2134,15 @@ Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
tokens.popFront();
e1 = new BinaryExpression(peek.str, e1, parseExpression(tokens));
break;
case "?": // is this the right precedence?
auto e = new TernaryExpression();
e.condition = e1;
tokens.requireNextToken(ScriptToken.Type.symbol, "?");
e.ifTrue = parseExpression(tokens);
tokens.requireNextToken(ScriptToken.Type.symbol, ":");
e.ifFalse = parseExpression(tokens);
e1 = e;
break;
case "~":
// FIXME: make sure this has the right associativity

View File

@ -488,7 +488,7 @@ struct Terminal {
import std.stdio;
import std.string;
if(!exists("/etc/termcap"))
//if(!exists("/etc/termcap"))
useBuiltinTermcap = true;
string current;

View File

@ -80,14 +80,11 @@ Document renderTemplate(string templateName, var context = var.emptyObject, var
// I don't particularly like this
void expandTemplate(Element root, var context) {
import std.string;
foreach(k, v; root.attributes) {
if(k == "onrender") {
// FIXME
}
string replaceThingInString(string v) {
auto idx = v.indexOf("<%=");
if(idx == -1)
continue;
return v;
auto n = v[0 .. idx];
auto r = v[idx + "<%=".length .. $];
@ -100,7 +97,16 @@ void expandTemplate(Element root, var context) {
import arsd.script;
auto res = interpret(code, context).get!string;
v = n ~ res ~ r;
return n ~ res ~ replaceThingInString(r);
}
foreach(k, v; root.attributes) {
if(k == "onrender") {
continue;
}
v = replaceThingInString(v);
root.setAttribute(k, v);
}
@ -178,6 +184,37 @@ void expandTemplate(Element root, var context) {
expandTemplate(ele, context);
}
}
if(root.hasAttribute("onrender")) {
var nc = var.emptyObject(context);
nc["this"] = wrapNativeObject(root);
nc["this"]["populateFrom"]._function = delegate var(var this_, var[] args) {
auto form = cast(Form) root;
if(form is null) return this_;
foreach(k, v; args[0]) {
populateForm(form, v, k.get!string);
}
return this_;
};
interpret(root.getAttribute("onrender"), nc);
root.removeAttribute("onrender");
}
}
void populateForm(Form form, var obj, string name) {
import std.string;
if(obj.payloadType == var.Type.Object) {
foreach(k, v; obj) {
auto fn = name.replace("%", k.get!string);
populateForm(form, v, fn ~ "["~k.get!string~"]");
}
} else {
//import std.stdio; writeln("SET ", name, " ", obj, " ", obj.payloadType);
form.setValue(name, obj.get!string);
}
}