parent
0f5c0be51f
commit
dc535d9d6d
@ -1,3 +1,4 @@ |
||||
recursive-include vasl_templates/webapp/config *.* |
||||
recursive-include vasl_templates/webapp/data *.* |
||||
recursive-include vasl_templates/webapp/static *.* |
||||
recursive-include vasl_templates/webapp/templates *.* |
||||
|
@ -0,0 +1,3 @@ |
||||
name = [{{SCENARIO_NAME}}] |
||||
loc = [{{SCENARIO_LOCATION}}] |
||||
date = [{{SCENARIO_DATE}}] |
@ -0,0 +1 @@ |
||||
VC: {{VICTORY_CONDITIONS}} |
@ -1,3 +1,26 @@ |
||||
""" Webapp handlers. """ |
||||
|
||||
import os |
||||
|
||||
from flask import jsonify |
||||
|
||||
from vasl_templates.webapp import app |
||||
from vasl_templates.webapp.config.constants import DATA_DIR |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
@app.route( "/templates" ) |
||||
def get_templates(): |
||||
"""Get the specified templates.""" |
||||
|
||||
# load the default templates |
||||
templates = {} |
||||
dname = os.path.join( DATA_DIR, "default-templates" ) |
||||
for fname in os.listdir(dname): |
||||
if os.path.splitext(fname)[1] != ".j2": |
||||
continue |
||||
fname2 = os.path.join( dname, fname ) |
||||
with open(fname2,"r") as fp: |
||||
templates[os.path.splitext(fname)[0]] = fp.read() |
||||
|
||||
return jsonify( templates ) |
||||
|
@ -0,0 +1,96 @@ |
||||
/* jQuery Growl |
||||
* Copyright 2015 Kevin Sylvestre |
||||
* 1.3.5 |
||||
*/ |
||||
.ontop, #growls-default, #growls-tl, #growls-tr, #growls-bl, #growls-br, #growls-tc, #growls-bc, #growls-cc, #growls-cl, #growls-cr { |
||||
z-index: 50000; |
||||
position: fixed; } |
||||
|
||||
#growls-default { |
||||
top: 10px; |
||||
right: 10px; } |
||||
#growls-tl { |
||||
top: 10px; |
||||
left: 10px; } |
||||
#growls-tr { |
||||
top: 10px; |
||||
right: 10px; } |
||||
#growls-bl { |
||||
bottom: 10px; |
||||
left: 10px; } |
||||
#growls-br { |
||||
bottom: 10px; |
||||
right: 10px; } |
||||
#growls-tc { |
||||
top: 10px; |
||||
right: 10px; |
||||
left: 10px; } |
||||
#growls-bc { |
||||
bottom: 10px; |
||||
right: 10px; |
||||
left: 10px; } |
||||
#growls-cc { |
||||
top: 50%; |
||||
left: 50%; |
||||
margin-left: -125px; } |
||||
#growls-cl { |
||||
top: 50%; |
||||
left: 10px; } |
||||
#growls-cr { |
||||
top: 50%; |
||||
right: 10px; } |
||||
#growls-tc .growl, #growls-bc .growl { |
||||
margin-left: auto; |
||||
margin-right: auto; } |
||||
|
||||
.growl { |
||||
opacity: 0.8; |
||||
filter: alpha(opacity=80); |
||||
position: relative; |
||||
border-radius: 4px; |
||||
-webkit-transition: all 0.4s ease-in-out; |
||||
-moz-transition: all 0.4s ease-in-out; |
||||
transition: all 0.4s ease-in-out; } |
||||
.growl.growl-incoming { |
||||
opacity: 0; |
||||
filter: alpha(opacity=0); } |
||||
.growl.growl-outgoing { |
||||
opacity: 0; |
||||
filter: alpha(opacity=0); } |
||||
.growl.growl-small { |
||||
width: 200px; |
||||
padding: 5px; |
||||
margin: 5px; } |
||||
.growl.growl-medium { |
||||
width: 250px; |
||||
padding: 10px; |
||||
margin: 10px; } |
||||
.growl.growl-large { |
||||
width: 300px; |
||||
padding: 15px; |
||||
margin: 15px; } |
||||
.growl.growl-default { |
||||
color: #FFF; |
||||
background: #7f8c8d; } |
||||
.growl.growl-error { |
||||
color: #FFF; |
||||
background: #C0392B; } |
||||
.growl.growl-notice { |
||||
color: #FFF; |
||||
background: #2ECC71; } |
||||
.growl.growl-warning { |
||||
color: #FFF; |
||||
background: #F39C12; } |
||||
.growl .growl-close { |
||||
cursor: pointer; |
||||
float: right; |
||||
font-size: 14px; |
||||
line-height: 18px; |
||||
font-weight: normal; |
||||
font-family: helvetica, verdana, sans-serif; } |
||||
.growl .growl-title { |
||||
font-size: 18px; |
||||
line-height: 24px; } |
||||
.growl .growl-message { |
||||
font-size: 14px; |
||||
line-height: 18px; } |
@ -0,0 +1,311 @@ |
||||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); |
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } |
||||
|
||||
// Generated by CoffeeScript 2.1.0
|
||||
(function () { |
||||
/* |
||||
jQuery Growl |
||||
Copyright 2015 Kevin Sylvestre |
||||
1.3.5 |
||||
*/ |
||||
"use strict"; |
||||
|
||||
var $, Animation, Growl; |
||||
|
||||
$ = jQuery; |
||||
|
||||
Animation = function () { |
||||
var Animation = function () { |
||||
function Animation() { |
||||
_classCallCheck(this, Animation); |
||||
} |
||||
|
||||
_createClass(Animation, null, [{ |
||||
key: "transition", |
||||
value: function transition($el) { |
||||
var el, ref, result, type; |
||||
el = $el[0]; |
||||
ref = this.transitions; |
||||
for (type in ref) { |
||||
result = ref[type]; |
||||
if (el.style[type] != null) { |
||||
return result; |
||||
} |
||||
} |
||||
} |
||||
}]); |
||||
|
||||
return Animation; |
||||
}(); |
||||
|
||||
; |
||||
|
||||
Animation.transitions = { |
||||
"webkitTransition": "webkitTransitionEnd", |
||||
"mozTransition": "mozTransitionEnd", |
||||
"oTransition": "oTransitionEnd", |
||||
"transition": "transitionend" |
||||
}; |
||||
|
||||
return Animation; |
||||
}(); |
||||
|
||||
Growl = function () { |
||||
var Growl = function () { |
||||
_createClass(Growl, null, [{ |
||||
key: "growl", |
||||
value: function growl() { |
||||
var settings = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; |
||||
|
||||
return new Growl(settings); |
||||
} |
||||
}]); |
||||
|
||||
function Growl() { |
||||
var settings = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; |
||||
|
||||
_classCallCheck(this, Growl); |
||||
|
||||
this.render = this.render.bind(this); |
||||
this.bind = this.bind.bind(this); |
||||
this.unbind = this.unbind.bind(this); |
||||
this.mouseEnter = this.mouseEnter.bind(this); |
||||
this.mouseLeave = this.mouseLeave.bind(this); |
||||
this.click = this.click.bind(this); |
||||
this.close = this.close.bind(this); |
||||
this.cycle = this.cycle.bind(this); |
||||
this.waitAndDismiss = this.waitAndDismiss.bind(this); |
||||
this.present = this.present.bind(this); |
||||
this.dismiss = this.dismiss.bind(this); |
||||
this.remove = this.remove.bind(this); |
||||
this.animate = this.animate.bind(this); |
||||
this.$growls = this.$growls.bind(this); |
||||
this.$growl = this.$growl.bind(this); |
||||
this.html = this.html.bind(this); |
||||
this.content = this.content.bind(this); |
||||
this.container = this.container.bind(this); |
||||
this.settings = $.extend({}, Growl.settings, settings); |
||||
this.initialize(this.settings.location); |
||||
this.render(); |
||||
} |
||||
|
||||
_createClass(Growl, [{ |
||||
key: "initialize", |
||||
value: function initialize(location) { |
||||
var id; |
||||
id = 'growls-' + location; |
||||
return $('body:not(:has(#' + id + '))').append('<div id="' + id + '" />'); |
||||
} |
||||
}, { |
||||
key: "render", |
||||
value: function render() { |
||||
var $growl; |
||||
$growl = this.$growl(); |
||||
this.$growls(this.settings.location).append($growl); |
||||
if (this.settings.fixed) { |
||||
this.present(); |
||||
} else { |
||||
this.cycle(); |
||||
} |
||||
} |
||||
}, { |
||||
key: "bind", |
||||
value: function bind() { |
||||
var $growl = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.$growl(); |
||||
|
||||
$growl.on("click", this.click); |
||||
if (this.settings.delayOnHover) { |
||||
$growl.on("mouseenter", this.mouseEnter); |
||||
$growl.on("mouseleave", this.mouseLeave); |
||||
} |
||||
return $growl.on("contextmenu", this.close).find("." + this.settings.namespace + "-close").on("click", this.close); |
||||
} |
||||
}, { |
||||
key: "unbind", |
||||
value: function unbind() { |
||||
var $growl = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.$growl(); |
||||
|
||||
$growl.off("click", this.click); |
||||
if (this.settings.delayOnHover) { |
||||
$growl.off("mouseenter", this.mouseEnter); |
||||
$growl.off("mouseleave", this.mouseLeave); |
||||
} |
||||
return $growl.off("contextmenu", this.close).find("." + this.settings.namespace + "-close").off("click", this.close); |
||||
} |
||||
}, { |
||||
key: "mouseEnter", |
||||
value: function mouseEnter(event) { |
||||
var $growl; |
||||
$growl = this.$growl(); |
||||
return $growl.stop(true, true); |
||||
} |
||||
}, { |
||||
key: "mouseLeave", |
||||
value: function mouseLeave(event) { |
||||
return this.waitAndDismiss(); |
||||
} |
||||
}, { |
||||
key: "click", |
||||
value: function click(event) { |
||||
if (this.settings.url != null) { |
||||
event.preventDefault(); |
||||
event.stopPropagation(); |
||||
return window.open(this.settings.url); |
||||
} |
||||
} |
||||
}, { |
||||
key: "close", |
||||
value: function close(event) { |
||||
var $growl; |
||||
event.preventDefault(); |
||||
event.stopPropagation(); |
||||
$growl = this.$growl(); |
||||
return $growl.stop().queue(this.dismiss).queue(this.remove); |
||||
} |
||||
}, { |
||||
key: "cycle", |
||||
value: function cycle() { |
||||
var $growl; |
||||
$growl = this.$growl(); |
||||
return $growl.queue(this.present).queue(this.waitAndDismiss()); |
||||
} |
||||
}, { |
||||
key: "waitAndDismiss", |
||||
value: function waitAndDismiss() { |
||||
var $growl; |
||||
$growl = this.$growl(); |
||||
return $growl.delay(this.settings.duration).queue(this.dismiss).queue(this.remove); |
||||
} |
||||
}, { |
||||
key: "present", |
||||
value: function present(callback) { |
||||
var $growl; |
||||
$growl = this.$growl(); |
||||
this.bind($growl); |
||||
return this.animate($growl, this.settings.namespace + "-incoming", 'out', callback); |
||||
} |
||||
}, { |
||||
key: "dismiss", |
||||
value: function dismiss(callback) { |
||||
var $growl; |
||||
$growl = this.$growl(); |
||||
this.unbind($growl); |
||||
return this.animate($growl, this.settings.namespace + "-outgoing", 'in', callback); |
||||
} |
||||
}, { |
||||
key: "remove", |
||||
value: function remove(callback) { |
||||
this.$growl().remove(); |
||||
return typeof callback === "function" ? callback() : void 0; |
||||
} |
||||
}, { |
||||
key: "animate", |
||||
value: function animate($element, name) { |
||||
var direction = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'in'; |
||||
var callback = arguments[3]; |
||||
|
||||
var transition; |
||||
transition = Animation.transition($element); |
||||
$element[direction === 'in' ? 'removeClass' : 'addClass'](name); |
||||
$element.offset().position; |
||||
$element[direction === 'in' ? 'addClass' : 'removeClass'](name); |
||||
if (callback == null) { |
||||
return; |
||||
} |
||||
if (transition != null) { |
||||
$element.one(transition, callback); |
||||
} else { |
||||
callback(); |
||||
} |
||||
} |
||||
}, { |
||||
key: "$growls", |
||||
value: function $growls(location) { |
||||
var base; |
||||
if (this.$_growls == null) { |
||||
this.$_growls = []; |
||||
} |
||||
return (base = this.$_growls)[location] != null ? base[location] : base[location] = $('#growls-' + location); |
||||
} |
||||
}, { |
||||
key: "$growl", |
||||
value: function $growl() { |
||||
return this.$_growl != null ? this.$_growl : this.$_growl = $(this.html()); |
||||
} |
||||
}, { |
||||
key: "html", |
||||
value: function html() { |
||||
return this.container(this.content()); |
||||
} |
||||
}, { |
||||
key: "content", |
||||
value: function content() { |
||||
return "<div class='" + this.settings.namespace + "-close'>" + this.settings.close + "</div>\n<div class='" + this.settings.namespace + "-title'>" + this.settings.title + "</div>\n<div class='" + this.settings.namespace + "-message'>" + this.settings.message + "</div>"; |
||||
} |
||||
}, { |
||||
key: "container", |
||||
value: function container(content) { |
||||
return "<div class='" + this.settings.namespace + " " + this.settings.namespace + "-" + this.settings.style + " " + this.settings.namespace + "-" + this.settings.size + "'>\n " + content + "\n</div>"; |
||||
} |
||||
}]); |
||||
|
||||
return Growl; |
||||
}(); |
||||
|
||||
; |
||||
|
||||
Growl.settings = { |
||||
namespace: 'growl', |
||||
duration: 3200, |
||||
close: "×", |
||||
location: "default", |
||||
style: "default", |
||||
size: "medium", |
||||
delayOnHover: true |
||||
}; |
||||
|
||||
return Growl; |
||||
}(); |
||||
|
||||
this.Growl = Growl; |
||||
|
||||
$.growl = function () { |
||||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; |
||||
|
||||
return Growl.growl(options); |
||||
}; |
||||
|
||||
$.growl.error = function () { |
||||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; |
||||
|
||||
var settings; |
||||
settings = { |
||||
title: "Error!", |
||||
style: "error" |
||||
}; |
||||
return $.growl($.extend(settings, options)); |
||||
}; |
||||
|
||||
$.growl.notice = function () { |
||||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; |
||||
|
||||
var settings; |
||||
settings = { |
||||
title: "Notice!", |
||||
style: "notice" |
||||
}; |
||||
return $.growl($.extend(settings, options)); |
||||
}; |
||||
|
||||
$.growl.warning = function () { |
||||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; |
||||
|
||||
var settings; |
||||
settings = { |
||||
title: "Warning!", |
||||
style: "warning" |
||||
}; |
||||
return $.growl($.extend(settings, options)); |
||||
}; |
||||
}).call(this); |
@ -0,0 +1,577 @@ |
||||
/*! |
||||
* Jinja Templating for JavaScript v0.1.8 |
||||
* https://github.com/sstur/jinja-js
|
||||
* |
||||
* This is a slimmed-down Jinja2 implementation [http://jinja.pocoo.org/]
|
||||
* |
||||
* In the interest of simplicity, it deviates from Jinja2 as follows: |
||||
* - Line statements, cycle, super, macro tags and block nesting are not implemented |
||||
* - auto escapes html by default (the filter is "html" not "e") |
||||
* - Only "html" and "safe" filters are built in |
||||
* - Filters are not valid in expressions; `foo|length > 1` is not valid |
||||
* - Expression Tests (`if num is odd`) not implemented (`is` translates to `==` and `isnot` to `!=`) |
||||
* |
||||
* Notes: |
||||
* - if property is not found, but method '_get' exists, it will be called with the property name (and cached) |
||||
* - `{% for n in obj %}` iterates the object's keys; get the value with `{% for n in obj %}{{ obj[n] }}{% endfor %}` |
||||
* - subscript notation `a[0]` takes literals or simple variables but not `a[item.key]` |
||||
* - `.2` is not a valid number literal; use `0.2` |
||||
* |
||||
*/ |
||||
/*global require, exports, module, define */ |
||||
var jinja; |
||||
(function(definition) { |
||||
if (typeof exports === 'object' && typeof module === 'object') { |
||||
// CommonJS/Node
|
||||
definition(require, exports, module); |
||||
//backwards compatibility
|
||||
if (typeof define === 'function') { |
||||
define('jinja', function() { |
||||
this.exports = module.exports; |
||||
}); |
||||
} |
||||
return; |
||||
} |
||||
if (typeof define === 'function') { |
||||
//AMD or Other
|
||||
return define.amd ? define(['require', 'exports'], definition) : define('jinja', definition); |
||||
} |
||||
definition(function() {}, jinja = {}); |
||||
})(function(require, jinja) { |
||||
"use strict"; |
||||
var STRINGS = /'(\\.|[^'])*'|"(\\.|[^"'"])*"/g; |
||||
var IDENTS_AND_NUMS = /([$_a-z][$\w]*)|([+-]?\d+(\.\d+)?)/g; |
||||
var NUMBER = /^[+-]?\d+(\.\d+)?$/; |
||||
//non-primitive literals (array and object literals)
|
||||
var NON_PRIMITIVES = /\[[@#~](,[@#~])*\]|\[\]|\{([@i]:[@#~])(,[@i]:[@#~])*\}|\{\}/g; |
||||
//bare identifiers such as variables and in object literals: {foo: 'value'}
|
||||
var IDENTIFIERS = /[$_a-z][$\w]*/ig; |
||||
var VARIABLES = /i(\.i|\[[@#i]\])*/g; |
||||
var ACCESSOR = /(\.i|\[[@#i]\])/g; |
||||
var OPERATORS = /(===?|!==?|>=?|<=?|&&|\|\||[+\-\*\/%])/g; |
||||
//extended (english) operators
|
||||
var EOPS = /(^|[^$\w])(and|or|not|is|isnot)([^$\w]|$)/g; |
||||
var LEADING_SPACE = /^\s+/; |
||||
var TRAILING_SPACE = /\s+$/; |
||||
|
||||
var START_TOKEN = /\{\{\{|\{\{|\{%|\{#/; |
||||
var TAGS = { |
||||
'{{{': /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?\}\}\}/, |
||||
'{{': /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?\}\}/, |
||||
'{%': /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?%\}/, |
||||
'{#': /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?#\}/ |
||||
}; |
||||
|
||||
var delimeters = { |
||||
'{%': 'directive', |
||||
'{{': 'output', |
||||
'{#': 'comment' |
||||
}; |
||||
|
||||
var operators = { |
||||
and: '&&', |
||||
or: '||', |
||||
not: '!', |
||||
is: '==', |
||||
isnot: '!=' |
||||
}; |
||||
|
||||
var constants = { |
||||
'true': true, |
||||
'false': false, |
||||
'null': null |
||||
}; |
||||
|
||||
function Parser() { |
||||
this.nest = []; |
||||
this.compiled = []; |
||||
this.childBlocks = 0; |
||||
this.parentBlocks = 0; |
||||
this.isSilent = false; |
||||
} |
||||
|
||||
Parser.prototype.push = function(line) { |
||||
if (!this.isSilent) { |
||||
this.compiled.push(line); |
||||
} |
||||
}; |
||||
|
||||
Parser.prototype.parse = function(src) { |
||||
this.tokenize(src); |
||||
return this.compiled; |
||||
}; |
||||
|
||||
Parser.prototype.tokenize = function(src) { |
||||
var lastEnd = 0, parser = this, trimLeading = false; |
||||
matchAll(src, START_TOKEN, function(open, index, src) { |
||||
//here we match the rest of the src against a regex for this tag
|
||||
var match = src.slice(index + open.length).match(TAGS[open]); |
||||
match = (match ? match[0] : ''); |
||||
//here we sub out strings so we don't get false matches
|
||||
var simplified = match.replace(STRINGS, '@'); |
||||
//if we don't have a close tag or there is a nested open tag
|
||||
if (!match || ~simplified.indexOf(open)) { |
||||
return index + 1; |
||||
} |
||||
var inner = match.slice(0, 0 - open.length); |
||||
//check for white-space collapse syntax
|
||||
if (inner.charAt(0) == '-') var wsCollapseLeft = true; |
||||
if (inner.slice(-1) == '-') var wsCollapseRight = true; |
||||
inner = inner.replace(/^-|-$/g, '').trim(); |
||||
//if we're in raw mode and we are not looking at an "endraw" tag, move along
|
||||
if (parser.rawMode && (open + inner) != '{%endraw') { |
||||
return index + 1; |
||||
} |
||||
var text = src.slice(lastEnd, index); |
||||
lastEnd = index + open.length + match.length; |
||||
if (trimLeading) text = trimLeft(text); |
||||
if (wsCollapseLeft) text = trimRight(text); |
||||
if (wsCollapseRight) trimLeading = true; |
||||
if (open == '{{{') { |
||||
//liquid-style: make {{{x}}} => {{x|safe}}
|
||||
open = '{{'; |
||||
inner += '|safe'; |
||||
} |
||||
parser.textHandler(text); |
||||
parser.tokenHandler(open, inner); |
||||
}); |
||||
var text = src.slice(lastEnd); |
||||
if (trimLeading) text = trimLeft(text); |
||||
this.textHandler(text); |
||||
}; |
||||
|
||||
Parser.prototype.textHandler = function(text) { |
||||
this.push('write(' + JSON.stringify(text) + ');'); |
||||
}; |
||||
|
||||
Parser.prototype.tokenHandler = function(open, inner) { |
||||
var type = delimeters[open]; |
||||
if (type == 'directive') { |
||||
this.compileTag(inner); |
||||
} else |
||||
if (type == 'output') { |
||||
var extracted = this.extractEnt(inner, STRINGS, '@'); |
||||
//replace || operators with ~
|
||||
extracted.src = extracted.src.replace(/\|\|/g, '~').split('|'); |
||||
//put back || operators
|
||||
extracted.src = extracted.src.map(function(part) { |
||||
return part.split('~').join('||'); |
||||
}); |
||||
var parts = this.injectEnt(extracted, '@'); |
||||
if (parts.length > 1) { |
||||
var filters = parts.slice(1).map(this.parseFilter.bind(this)); |
||||
this.push('filter(' + this.parseExpr(parts[0]) + ',' + filters.join(',') + ');'); |
||||
} else { |
||||
this.push('filter(' + this.parseExpr(parts[0]) + ');'); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
Parser.prototype.compileTag = function(str) { |
||||
var directive = str.split(' ')[0]; |
||||
var handler = tagHandlers[directive]; |
||||
if (!handler) { |
||||
throw new Error('Invalid tag: ' + str); |
||||
} |
||||
handler.call(this, str.slice(directive.length).trim()); |
||||
}; |
||||
|
||||
Parser.prototype.parseFilter = function(src) { |
||||
src = src.trim(); |
||||
var match = src.match(/[:(]/); |
||||
var i = match ? match.index : -1; |
||||
if (i < 0) return JSON.stringify([src]); |
||||
var name = src.slice(0, i); |
||||
var args = src.charAt(i) == ':' ? src.slice(i + 1) : src.slice(i + 1, -1); |
||||
args = this.parseExpr(args, {terms: true}); |
||||
return '[' + JSON.stringify(name) + ',' + args + ']'; |
||||
}; |
||||
|
||||
Parser.prototype.extractEnt = function(src, regex, placeholder) { |
||||
var subs = [], isFunc = typeof placeholder == 'function'; |
||||
src = src.replace(regex, function(str) { |
||||
var replacement = isFunc ? placeholder(str) : placeholder; |
||||
if (replacement) { |
||||
subs.push(str); |
||||
return replacement; |
||||
} |
||||
return str; |
||||
}); |
||||
return {src: src, subs: subs}; |
||||
}; |
||||
|
||||
Parser.prototype.injectEnt = function(extracted, placeholder) { |
||||
var src = extracted.src, subs = extracted.subs, isArr = Array.isArray(src); |
||||
var arr = (isArr) ? src : [src]; |
||||
var re = new RegExp('[' + placeholder + ']', 'g'), i = 0; |
||||
arr.forEach(function(src, index) { |
||||
arr[index] = src.replace(re, function() { |
||||
return subs[i++]; |
||||
}); |
||||
}); |
||||
return isArr ? arr : arr[0]; |
||||
}; |
||||
|
||||
//replace complex literals without mistaking subscript notation with array literals
|
||||
Parser.prototype.replaceComplex = function(s) { |
||||
var parsed = this.extractEnt(s, /i(\.i|\[[@#i]\])+/g, 'v'); |
||||
parsed.src = parsed.src.replace(NON_PRIMITIVES, '~'); |
||||
return this.injectEnt(parsed, 'v'); |
||||
}; |
||||
|
||||
//parse expression containing literals (including objects/arrays) and variables (including dot and subscript notation)
|
||||
//valid expressions: `a + 1 > b.c or c == null`, `a and b[1] != c`, `(a < b) or (c < d and e)`, 'a || [1]`
|
||||
Parser.prototype.parseExpr = function(src, opts) { |
||||
opts = opts || {}; |
||||
//extract string literals -> @
|
||||
var parsed1 = this.extractEnt(src, STRINGS, '@'); |
||||
//note: this will catch {not: 1} and a.is; could we replace temporarily and then check adjacent chars?
|
||||
parsed1.src = parsed1.src.replace(EOPS, function(s, before, op, after) { |
||||
return (op in operators) ? before + operators[op] + after : s; |
||||
}); |
||||
//sub out non-string literals (numbers/true/false/null) -> #
|
||||
// the distinction is necessary because @ can be object identifiers, # cannot
|
||||
var parsed2 = this.extractEnt(parsed1.src, IDENTS_AND_NUMS, function(s) { |
||||
return (s in constants || NUMBER.test(s)) ? '#' : null; |
||||
}); |
||||
//sub out object/variable identifiers -> i
|
||||
var parsed3 = this.extractEnt(parsed2.src, IDENTIFIERS, 'i'); |
||||
//remove white-space
|
||||
parsed3.src = parsed3.src.replace(/\s+/g, ''); |
||||
|
||||
//the rest of this is simply to boil the expression down and check validity
|
||||
var simplified = parsed3.src; |
||||
//sub out complex literals (objects/arrays) -> ~
|
||||
// the distinction is necessary because @ and # can be subscripts but ~ cannot
|
||||
while (simplified != (simplified = this.replaceComplex(simplified))); |
||||
//now @ represents strings, # represents other primitives and ~ represents non-primitives
|
||||
//replace complex variables (those with dot/subscript accessors) -> v
|
||||
while (simplified != (simplified = simplified.replace(/i(\.i|\[[@#i]\])+/, 'v'))); |
||||
//empty subscript or complex variables in subscript, are not permitted
|
||||
simplified = simplified.replace(/[iv]\[v?\]/g, 'x'); |
||||
//sub in "i" for @ and # and ~ and v (now "i" represents all literals, variables and identifiers)
|
||||
simplified = simplified.replace(/[@#~v]/g, 'i'); |
||||
//sub out operators
|
||||
simplified = simplified.replace(OPERATORS, '%'); |
||||
//allow 'not' unary operator
|
||||
simplified = simplified.replace(/!+[i]/g, 'i'); |
||||
var terms = opts.terms ? simplified.split(',') : [simplified]; |
||||
terms.forEach(function(term) { |
||||
//simplify logical grouping
|
||||
while (term != (term = term.replace(/\(i(%i)*\)/g, 'i'))); |
||||
if (!term.match(/^i(%i)*$/)) { |
||||
throw new Error('Invalid expression: ' + src); |
||||
} |
||||
}); |
||||
parsed3.src = parsed3.src.replace(VARIABLES, this.parseVar.bind(this)); |
||||
parsed2.src = this.injectEnt(parsed3, 'i'); |
||||
parsed1.src = this.injectEnt(parsed2, '#'); |
||||
return this.injectEnt(parsed1, '@'); |
||||
}; |
||||
|
||||
Parser.prototype.parseVar = function(src) { |
||||
var args = Array.prototype.slice.call(arguments); |
||||
var str = args.pop(), index = args.pop(); |
||||
//quote bare object identifiers (might be a reserved word like {while: 1})
|
||||
if (src == 'i' && str.charAt(index + 1) == ':') { |
||||
return '"i"'; |
||||
} |
||||
var parts = ['"i"']; |
||||
src.replace(ACCESSOR, function(part) { |
||||
if (part == '.i') { |
||||
parts.push('"i"'); |
||||
} else |
||||
if (part == '[i]') { |
||||
parts.push('get("i")'); |
||||
} else { |
||||
parts.push(part.slice(1, -1)); |
||||
} |
||||
}); |
||||
return 'get(' + parts.join(',') + ')'; |
||||
}; |
||||
|
||||
//escapes a name to be used as a javascript identifier
|
||||
Parser.prototype.escName = function(str) { |
||||
return str.replace(/\W/g, function(s) { |
||||
return '$' + s.charCodeAt(0).toString(16); |
||||
}); |
||||
}; |
||||
|
||||
Parser.prototype.parseQuoted = function(str) { |
||||
if (str.charAt(0) == "'") { |
||||
str = str.slice(1, -1).replace(/\\.|"/, function(s) { |
||||
if (s == "\\'") return "'"; |
||||
return s.charAt(0) == '\\' ? s : ('\\' + s); |
||||
}); |
||||
str = '"' + str + '"'; |
||||
} |
||||
//todo: try/catch or deal with invalid characters (linebreaks, control characters)
|
||||
return JSON.parse(str); |
||||
}; |
||||
|
||||
|
||||
//the context 'this' inside tagHandlers is the parser instance
|
||||
var tagHandlers = { |
||||
'if': function(expr) { |
||||
this.push('if (' + this.parseExpr(expr) + ') {'); |
||||
this.nest.unshift('if'); |
||||
}, |
||||
'else': function() { |
||||
if (this.nest[0] == 'for') { |
||||
this.push('}, function() {'); |
||||
} else { |
||||
this.push('} else {'); |
||||
} |
||||
}, |
||||
'elseif': function(expr) { |
||||
this.push('} else if (' + this.parseExpr(expr) + ') {'); |
||||
}, |
||||
'endif': function() { |
||||
this.nest.shift(); |
||||
this.push('}'); |
||||
}, |
||||
'for': function(str) { |
||||
var i = str.indexOf(' in '); |
||||
var name = str.slice(0, i).trim(); |
||||
var expr = str.slice(i + 4).trim(); |
||||
this.push('each(' + this.parseExpr(expr) + ',' + JSON.stringify(name) + ',function() {'); |
||||
this.nest.unshift('for'); |
||||
}, |
||||
'endfor': function() { |
||||
this.nest.shift(); |
||||
this.push('});'); |
||||
}, |
||||
'raw': function() { |
||||
this.rawMode = true; |
||||
}, |
||||
'endraw': function() { |
||||
this.rawMode = false; |
||||
}, |
||||
'set': function(stmt) { |
||||
var i = stmt.indexOf('='); |
||||
var name = stmt.slice(0, i).trim(); |
||||
var expr = stmt.slice(i + 1).trim(); |
||||
this.push('set(' + JSON.stringify(name) + ',' + this.parseExpr(expr) + ');'); |
||||
}, |
||||
'block': function(name) { |
||||
if (this.isParent) { |
||||
++this.parentBlocks; |
||||
var blockName = 'block_' + (this.escName(name) || this.parentBlocks); |
||||
this.push('block(typeof ' + blockName + ' == "function" ? ' + blockName + ' : function() {'); |
||||
} else |
||||
if (this.hasParent) { |
||||
this.isSilent = false; |
||||
++this.childBlocks; |
||||
blockName = 'block_' + (this.escName(name) || this.childBlocks); |
||||
this.push('function ' + blockName + '() {'); |
||||
} |
||||
this.nest.unshift('block'); |
||||
}, |
||||
'endblock': function() { |
||||
this.nest.shift(); |
||||
if (this.isParent) { |
||||
this.push('});'); |
||||
} else |
||||
if (this.hasParent) { |
||||
this.push('}'); |
||||
this.isSilent = true; |
||||
} |
||||
}, |
||||
'extends': function(name) { |
||||
name = this.parseQuoted(name); |
||||
var parentSrc = this.readTemplateFile(name); |
||||
this.isParent = true; |
||||
this.tokenize(parentSrc); |
||||
this.isParent = false; |
||||
this.hasParent = true; |
||||
//silence output until we enter a child block
|
||||
this.isSilent = true; |
||||
}, |
||||
'include': function(name) { |
||||
name = this.parseQuoted(name); |
||||
var incSrc = this.readTemplateFile(name); |
||||
this.isInclude = true; |
||||
this.tokenize(incSrc); |
||||
this.isInclude = false; |
||||
} |
||||
}; |
||||
|
||||
//liquid style
|
||||
tagHandlers.assign = tagHandlers.set; |
||||
//python/django style
|
||||
tagHandlers.elif = tagHandlers.elseif; |
||||
|
||||
var getRuntime = function runtime(data, opts) { |
||||
var defaults = {autoEscape: 'html'}; |
||||
var _toString = Object.prototype.toString; |
||||
var _hasOwnProperty = Object.prototype.hasOwnProperty; |
||||
var getKeys = Object.keys || function(obj) { |
||||
var keys = []; |
||||
for (var n in obj) if (_hasOwnProperty.call(obj, n)) keys.push(n); |
||||
return keys; |
||||
}; |
||||
var isArray = Array.isArray || function(obj) { |
||||
return _toString.call(obj) === '[object Array]'; |
||||
}; |
||||
var create = Object.create || function(obj) { |
||||
function F() {} |
||||
F.prototype = obj; |
||||
return new F(); |
||||
}; |
||||
var toString = function(val) { |
||||
if (val == null) return ''; |
||||
return (typeof val.toString == 'function') ? val.toString() : _toString.call(val); |
||||
}; |
||||
var extend = function(dest, src) { |
||||
var keys = getKeys(src); |
||||
for (var i = 0, len = keys.length; i < len; i++) { |
||||
var key = keys[i]; |
||||
dest[key] = src[key]; |
||||
} |
||||
return dest; |
||||
}; |
||||
//get a value, lexically, starting in current context; a.b -> get("a","b")
|
||||
var get = function() { |
||||
var val, n = arguments[0], c = stack.length; |
||||
while (c--) { |
||||
val = stack[c][n]; |
||||
if (typeof val != 'undefined') break; |
||||
} |
||||
for (var i = 1, len = arguments.length; i < len; i++) { |
||||
if (val == null) continue; |
||||
n = arguments[i]; |
||||
val = (_hasOwnProperty.call(val, n)) ? val[n] : (typeof val._get == 'function' ? (val[n] = val._get(n)) : null); |
||||
} |
||||
return (val == null) ? null : val; |
||||
}; |
||||
var set = function(n, val) { |
||||
stack[stack.length - 1][n] = val; |
||||
}; |
||||
var push = function(ctx) { |
||||
stack.push(ctx || {}); |
||||
}; |
||||
var pop = function() { |
||||
stack.pop(); |
||||
}; |
||||
var write = function(str) { |
||||
output.push(str); |
||||
}; |
||||
var filter = function(val) { |
||||
for (var i = 1, len = arguments.length; i < len; i++) { |
||||
var arr = arguments[i], name = arr[0], filter = filters[name]; |
||||
if (filter) { |
||||
arr[0] = val; |
||||
//now arr looks like [val, arg1, arg2]
|
||||
val = filter.apply(data, arr); |
||||
} else { |
||||
throw new Error('Invalid filter: ' + name); |
||||
} |
||||
} |
||||
if (opts.autoEscape && name != opts.autoEscape && name != 'safe') { |
||||
//auto escape if not explicitly safe or already escaped
|
||||
val = filters[opts.autoEscape].call(data, val); |
||||
} |
||||
output.push(val); |
||||
}; |
||||
var each = function(obj, loopvar, fn1, fn2) { |
||||
if (obj == null) return; |
||||
var arr = isArray(obj) ? obj : getKeys(obj), len = arr.length; |
||||
var ctx = {loop: {length: len, first: arr[0], last: arr[len - 1]}}; |
||||
push(ctx); |
||||
for (var i = 0; i < len; i++) { |
||||
extend(ctx.loop, {index: i + 1, index0: i}); |
||||
fn1(ctx[loopvar] = arr[i]); |
||||
} |
||||
if (len == 0 && fn2) fn2(); |
||||
pop(); |
||||
}; |
||||
var block = function(fn) { |
||||
push(); |
||||
fn(); |
||||
pop(); |
||||
}; |
||||
var render = function() { |
||||
return output.join(''); |
||||
}; |
||||
data = data || {}; |
||||
opts = extend(defaults, opts || {}); |
||||
var filters = extend({ |
||||
html: function(val) { |
||||
return toString(val) |
||||
.split('&').join('&') |
||||
.split('<').join('<') |
||||
.split('>').join('>') |
||||
.split('"').join('"'); |
||||
}, |
||||
safe: function(val) { |
||||
return val; |
||||
} |
||||
}, opts.filters || {}); |
||||
var stack = [create(data || {})], output = []; |
||||
return {get: get, set: set, push: push, pop: pop, write: write, filter: filter, each: each, block: block, render: render}; |
||||
}; |
||||
|
||||
var runtime; |
||||
|
||||
jinja.compile = function(markup, opts) { |
||||
opts = opts || {}; |
||||
var parser = new Parser(); |
||||
parser.readTemplateFile = this.readTemplateFile; |
||||
var code = []; |
||||
code.push('function render($) {'); |
||||
code.push('var get = $.get, set = $.set, push = $.push, pop = $.pop, write = $.write, filter = $.filter, each = $.each, block = $.block;'); |
||||
code.push.apply(code, parser.parse(markup)); |
||||
code.push('return $.render();'); |
||||
code.push('}'); |
||||
code = code.join('\n'); |
||||
if (opts.runtime === false) { |
||||
var fn = new Function('data', 'options', 'return (' + code + ')(runtime(data, options))'); |
||||
} else { |
||||
runtime = runtime || (runtime = getRuntime.toString()); |
||||
fn = new Function('data', 'options', 'return (' + code + ')((' + runtime + ')(data, options))'); |
||||
} |
||||
return {render: fn}; |
||||
}; |
||||
|
||||
jinja.render = function(markup, data, opts) { |
||||
var tmpl = jinja.compile(markup); |
||||
return tmpl.render(data, opts); |
||||
}; |
||||
|
||||
jinja.templateFiles = []; |
||||
|
||||
jinja.readTemplateFile = function(name) { |
||||
var templateFiles = this.templateFiles || []; |
||||
var templateFile = templateFiles[name]; |
||||
if (templateFile == null) { |
||||
throw new Error('Template file not found: ' + name); |
||||
} |
||||
return templateFile; |
||||
}; |
||||
|
||||
|
||||
/*! |
||||
* Helpers |
||||
*/ |
||||
|
||||
function trimLeft(str) { |
||||
return str.replace(LEADING_SPACE, ''); |
||||
} |
||||
|
||||
function trimRight(str) { |
||||
return str.replace(TRAILING_SPACE, ''); |
||||
} |
||||
|
||||
function matchAll(str, reg, fn) { |
||||
//copy as global
|
||||
reg = new RegExp(reg.source, 'g' + (reg.ignoreCase ? 'i' : '') + (reg.multiline ? 'm' : '')); |
||||
var match; |
||||
while ((match = reg.exec(str))) { |
||||
var result = fn(match[0], match.index, str); |
||||
if (typeof result == 'number') { |
||||
reg.lastIndex = result; |
||||
} |
||||
} |
||||
} |
||||
|
||||
}); |
@ -0,0 +1,112 @@ |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function copyToClipboard( val ) |
||||
{ |
||||
// IE-specific code path to prevent textarea being shown while dialog is visible
|
||||
if ( window.clipboardData && window.clipboardData.setData ) { |
||||
clipboardData.setData( "Text", val ) ; |
||||
return ; |
||||
} |
||||
|
||||
if ( document.queryCommandSupported && document.queryCommandSupported("copy") ) { |
||||
// create a textarea to hold the content
|
||||
var textarea = document.createElement( "textarea" ) ; |
||||
textarea.style.position = "fixed" ; // prevent scrolling to bottom in MS Edge
|
||||
document.body.appendChild( textarea ) ; |
||||
textarea.textContent = val ; |
||||
// copy the textarea content to the clipboard
|
||||
textarea.select() ; |
||||
try { |
||||
document.execCommand( "copy" ) ; |
||||
if ( getUrlParam("log-clipboard") ) |
||||
console.log( "CLIPBOARD:", val ) ; |
||||
} |
||||
catch( ex ) { |
||||
showErrorMsg( "Can't copy to the clipboard:<pre>" + escapeHTML(ex) + "</pre>" ) ; |
||||
} |
||||
finally { |
||||
document.body.removeChild( textarea ) ; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function showInfoMsg( msg ) |
||||
{ |
||||
// show the informational message
|
||||
$.growl( { |
||||
style: "notice", |
||||
title: null, |
||||
message: msg, |
||||
location: "br", |
||||
} ) ; |
||||
storeMsgForTestSuite( "_last-info_", msg ) ; |
||||
} |
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
function showWarningMsg( msg ) |
||||
{ |
||||
// show the warning message
|
||||
$.growl( { |
||||
style: "warning", |
||||
title: null, |
||||
message: msg, |
||||
location: "br", |
||||
} ) ; |
||||
storeMsgForTestSuite( "_last-warning_", msg ) ; |
||||
} |
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
function showErrorMsg( msg ) |
||||
{ |
||||
// show the error message
|
||||
$.growl( { |
||||
style: "error", |
||||
title: null, |
||||
message: msg, |
||||
location: "br", |
||||
fixed: true, |
||||
} ) ; |
||||
storeMsgForTestSuite( "_last-error_", msg ) ; |
||||
} |
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
function storeMsgForTestSuite( id, msg ) |
||||
{ |
||||
// store a message for the test suite
|
||||
if ( ! getUrlParam( "store_msgs" ) ) |
||||
return ; |
||||
var $elem = $( "#"+id ) ; |
||||
if ( $elem.length === 0 ) { |
||||
// NOTE: The <div> we store the message in must be visible, otherwise
|
||||
// Selenium doesn't return any text for it :-/
|
||||
$elem = $( "<div id='" + id + "' style='z-index-999;'></div>" ) ; |
||||
$("body").append( $elem ) ; |
||||
} |
||||
$elem.html( msg ) ; |
||||
} |
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
function getUrlParam( param ) |
||||
{ |
||||
// look for the specified URL parameter
|
||||
var url = window.location.search.substring( 1 ) ; |
||||
var params = url.split( "&" ) ; |
||||
for ( var i=0 ; i < params.length ; i++ ) { |
||||
var keyval = params[i].split( "=" ) ; |
||||
if ( keyval[0] == param ) |
||||
return keyval[1] ; |
||||
} |
||||
} |
||||
|
||||
function escapeHTML( val ) |
||||
{ |
||||
// escape HTML
|
||||
return new Option(val).innerHTML ; |
||||
} |
@ -1,3 +1,97 @@ |
||||
""" Test response generation. """ |
||||
""" Test HTML snippet generation. """ |
||||
|
||||
from vasl_templates.webapp.tests.utils import get_clipboard, get_stored_msg, find_child |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
# initialize |
||||
def _test_snippet( webdriver, template_id, params, expected, expected2 ): |
||||
"""Do a single test.""" |
||||
|
||||
# set the template parameters |
||||
for key,val in params.items(): |
||||
elem = find_child( webdriver, "input[name='{}']".format(key) ) |
||||
if not elem: |
||||
elem = find_child( webdriver, "textarea[name='{}']".format(key) ) |
||||
elem.clear() |
||||
if val: |
||||
elem.send_keys( val ) |
||||
|
||||
# generate the snippet |
||||
submit = find_child( webdriver, "input[class='generate'][data-id='{}']".format(template_id) ) |
||||
submit.click() |
||||
snippet = get_clipboard() |
||||
lines = [ l.strip() for l in snippet.split("\n") ] |
||||
snippet = " | ".join( l for l in lines if l ) |
||||
assert snippet == expected |
||||
|
||||
# check warnings for mandatory parameters |
||||
last_warning = get_stored_msg( "_last-warning_" ) or "" |
||||
param_names = [ "scenario name", "scenario location", "scenario date" ] |
||||
for pname in param_names: |
||||
if pname in expected2: |
||||
assert pname in last_warning |
||||
else: |
||||
assert pname not in last_warning |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
def test_scenario_snippets( webapp, webdriver ): |
||||
"""Test HTML snippet generation.""" |
||||
|
||||
# initialize |
||||
webdriver.get( webapp.url_for( "main", store_msgs=1 ) ) |
||||
|
||||
# generate a SCENARIO snippet |
||||
_test_snippet( webdriver, "scenario", { |
||||
"scenario_name": "my scenario", |
||||
"scenario_location": "here", |
||||
"scenario_date": "now", |
||||
}, |
||||
"name = [my scenario] | loc = [here] | date = [now]", |
||||
[] |
||||
) |
||||
|
||||
# generate a SCENARIO snippet with some fields missing |
||||
_test_snippet( webdriver, "scenario", { |
||||
"scenario_name": "my scenario", |
||||
"scenario_location": None, |
||||
"scenario_date": None, |
||||
}, |
||||
"name = [my scenario] | loc = [] | date = []", |
||||
[ "scenario date" ], |
||||
) |
||||
|
||||
# generate a SCENARIO snippet with all fields missing |
||||
_test_snippet( webdriver, "scenario", { |
||||
"scenario_name": None, |
||||
"scenario_location": None, |
||||
"scenario_date": None, |
||||
}, |
||||
"name = [] | loc = [] | date = []", |
||||
[ "scenario name", "scenario date" ], |
||||
) |
||||
|
||||
# --------------------------------------------------------------------- |
||||
|
||||
def test_vc_snippets( webapp, webdriver ): |
||||
"""Test HTML snippet generation.""" |
||||
|
||||
# initialize |
||||
webdriver.get( webapp.url_for( "main", store_msgs=1 ) ) |
||||
|
||||
# generate a VC snippet |
||||
_test_snippet( webdriver, "victory_conditions", { |
||||
"victory_conditions": "Kill 'Em All!", |
||||
}, |
||||
"VC: Kill 'Em All!", |
||||
[] |
||||
) |
||||
|
||||
# generate a VC snippet |
||||
_test_snippet( webdriver, "victory_conditions", { |
||||
"victory_conditions": "", |
||||
}, |
||||
"VC:", |
||||
[] |
||||
) |
||||
|
Loading…
Reference in new issue