parent
0f5c0be51f
commit
dc535d9d6d
@ -1,3 +1,4 @@ |
|||||||
recursive-include vasl_templates/webapp/config *.* |
recursive-include vasl_templates/webapp/config *.* |
||||||
|
recursive-include vasl_templates/webapp/data *.* |
||||||
recursive-include vasl_templates/webapp/static *.* |
recursive-include vasl_templates/webapp/static *.* |
||||||
recursive-include vasl_templates/webapp/templates *.* |
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. """ |
""" 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