From 3cd789f028d6993f00a4aabb32bef8a2293bffca Mon Sep 17 00:00:00 2001 From: Radomir Dopieralski Date: Tue, 20 May 2014 13:48:46 +0200 Subject: [PATCH] Version 2.0.0 of Hogan.js library packaged for Xstatic As used by Horizon. --- .gitignore | 9 + MANIFEST.in | 8 + README.txt | 13 + setup.py | 27 ++ xstatic/__init__.py | 1 + xstatic/pkg/__init__.py | 1 + xstatic/pkg/hogan/__init__.py | 49 +++ xstatic/pkg/hogan/data/hogan.js | 576 ++++++++++++++++++++++++++++++++ 8 files changed, 684 insertions(+) create mode 100644 .gitignore create mode 100644 MANIFEST.in create mode 100644 README.txt create mode 100644 setup.py create mode 100644 xstatic/__init__.py create mode 100644 xstatic/pkg/__init__.py create mode 100644 xstatic/pkg/hogan/__init__.py create mode 100644 xstatic/pkg/hogan/data/hogan.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3085b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.pyc +*.sw? +*.sqlite3 +.DS_STORE +*.egg-info +.venv +.tox +build +dist diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..ed2f016 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include README.txt +recursive-include xstatic/pkg/hogan * + +global-exclude *.pyc +global-exclude *.pyo +global-exclude *.orig +global-exclude *.rej + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..9f46cf8 --- /dev/null +++ b/README.txt @@ -0,0 +1,13 @@ +XStatic-D3 +-------------- + +Hogan JavaScript library packaged for setuptools (easy_install) / pip. + +This package is intended to be used by **any** project that needs these files. + +It intentionally does **not** provide any extra code except some metadata +**nor** has any extra requirements. You MAY use some minimal support code from +the XStatic base package, if you like. + +You can find more info about the xstatic packaging way in the package `XStatic`. + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ea55db4 --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +from xstatic.pkg import d3 as xs + +# The README.txt file should be written in reST so that PyPI can use +# it to generate your project's PyPI page. +long_description = open('README.txt').read() + +from setuptools import setup, find_packages + +setup( + name=xs.PACKAGE_NAME, + version=xs.PACKAGE_VERSION, + description=xs.DESCRIPTION, + long_description=long_description, + classifiers=xs.CLASSIFIERS, + keywords=xs.KEYWORDS, + maintainer=xs.MAINTAINER, + maintainer_email=xs.MAINTAINER_EMAIL, + license=xs.LICENSE, + url=xs.HOMEPAGE, + platforms=xs.PLATFORMS, + packages=find_packages(), + namespace_packages=['xstatic', 'xstatic.pkg', ], + include_package_data=True, + zip_safe=False, + install_requires=[], # nothing! :) + # if you like, you MAY use the 'XStatic' package. +) diff --git a/xstatic/__init__.py b/xstatic/__init__.py new file mode 100644 index 0000000..de40ea7 --- /dev/null +++ b/xstatic/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/xstatic/pkg/__init__.py b/xstatic/pkg/__init__.py new file mode 100644 index 0000000..de40ea7 --- /dev/null +++ b/xstatic/pkg/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/xstatic/pkg/hogan/__init__.py b/xstatic/pkg/hogan/__init__.py new file mode 100644 index 0000000..7963a9d --- /dev/null +++ b/xstatic/pkg/hogan/__init__.py @@ -0,0 +1,49 @@ +""" +XStatic resource package + +See package 'XStatic' for documentation and basic tools. +""" + +DISPLAY_NAME = 'Hogan' # official name, upper/lowercase allowed, no spaces +PACKAGE_NAME = 'XStatic-%s' % DISPLAY_NAME # name used for PyPi + +NAME = __name__.split('.')[-1] # package name (e.g. 'foo' or 'foo_bar') + # please use a all-lowercase valid python + # package name + +VERSION = '2.0.0' # version of the packaged files, please use the upstream + # version number +BUILD = '1' # our package build number, so we can release new builds + # with fixes for xstatic stuff. +PACKAGE_VERSION = VERSION + '.' + BUILD # version used for PyPi + +DESCRIPTION = "%s %s (XStatic packaging standard)" % (DISPLAY_NAME, VERSION) + +PLATFORMS = 'any' +CLASSIFIERS = [] +KEYWORDS = '%s xstatic' % NAME + +# XStatic-* package maintainer: +MAINTAINER = 'Radomir Dopieralski' +MAINTAINER_EMAIL = 'openstack@sheep.art.pl' + +# this refers to the project homepage of the stuff we packaged: +HOMEPAGE = 'http://twitter.github.io/hogan.js/' + +# this refers to all files: +LICENSE = '(same as %s)' % DISPLAY_NAME + +from os.path import join, dirname +BASE_DIR = join(dirname(__file__), 'data') +# linux package maintainers just can point to their file locations like this: +#BASE_DIR = '/usr/share/javascript/hogan' + +LOCATIONS = { + # CDN locations (if no public CDN exists, use an empty dict) + # if value is a string, it is a base location, just append relative + # path/filename. if value is a dict, do another lookup using the + # relative path/filename you want. + # your relative path/filenames should usually be without version + # information, because either the base dir/url is exactly for this + # version or the mapping will care for accessing this version. +} diff --git a/xstatic/pkg/hogan/data/hogan.js b/xstatic/pkg/hogan/data/hogan.js new file mode 100644 index 0000000..43a6707 --- /dev/null +++ b/xstatic/pkg/hogan/data/hogan.js @@ -0,0 +1,576 @@ +/* + * Copyright 2011 Twitter, Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +var Hogan = {}; + +(function (Hogan, useArrayBuffer) { + Hogan.Template = function (renderFunc, text, compiler, options) { + this.r = renderFunc || this.r; + this.c = compiler; + this.options = options; + this.text = text || ''; + this.buf = (useArrayBuffer) ? [] : ''; + } + + Hogan.Template.prototype = { + // render: replaced by generated code. + r: function (context, partials, indent) { return ''; }, + + // variable escaping + v: hoganEscape, + + // triple stache + t: coerceToString, + + render: function render(context, partials, indent) { + return this.ri([context], partials || {}, indent); + }, + + // render internal -- a hook for overrides that catches partials too + ri: function (context, partials, indent) { + return this.r(context, partials, indent); + }, + + // tries to find a partial in the curent scope and render it + rp: function(name, context, partials, indent) { + var partial = partials[name]; + + if (!partial) { + return ''; + } + + if (this.c && typeof partial == 'string') { + partial = this.c.compile(partial, this.options); + } + + return partial.ri(context, partials, indent); + }, + + // render a section + rs: function(context, partials, section) { + var tail = context[context.length - 1]; + + if (!isArray(tail)) { + section(context, partials, this); + return; + } + + for (var i = 0; i < tail.length; i++) { + context.push(tail[i]); + section(context, partials, this); + context.pop(); + } + }, + + // maybe start a section + s: function(val, ctx, partials, inverted, start, end, tags) { + var pass; + + if (isArray(val) && val.length === 0) { + return false; + } + + if (typeof val == 'function') { + val = this.ls(val, ctx, partials, inverted, start, end, tags); + } + + pass = (val === '') || !!val; + + if (!inverted && pass && ctx) { + ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]); + } + + return pass; + }, + + // find values with dotted names + d: function(key, ctx, partials, returnFound) { + var names = key.split('.'), + val = this.f(names[0], ctx, partials, returnFound), + cx = null; + + if (key === '.' && isArray(ctx[ctx.length - 2])) { + return ctx[ctx.length - 1]; + } + + for (var i = 1; i < names.length; i++) { + if (val && typeof val == 'object' && names[i] in val) { + cx = val; + val = val[names[i]]; + } else { + val = ''; + } + } + + if (returnFound && !val) { + return false; + } + + if (!returnFound && typeof val == 'function') { + ctx.push(cx); + val = this.lv(val, ctx, partials); + ctx.pop(); + } + + return val; + }, + + // find values with normal names + f: function(key, ctx, partials, returnFound) { + var val = false, + v = null, + found = false; + + for (var i = ctx.length - 1; i >= 0; i--) { + v = ctx[i]; + if (v && typeof v == 'object' && key in v) { + val = v[key]; + found = true; + break; + } + } + + if (!found) { + return (returnFound) ? false : ""; + } + + if (!returnFound && typeof val == 'function') { + val = this.lv(val, ctx, partials); + } + + return val; + }, + + // higher order templates + ho: function(val, cx, partials, text, tags) { + var compiler = this.c; + var options = this.options; + options.delimiters = tags; + var text = val.call(cx, text); + text = (text == null) ? String(text) : text.toString(); + this.b(compiler.compile(text, options).render(cx, partials)); + return false; + }, + + // template result buffering + b: (useArrayBuffer) ? function(s) { this.buf.push(s); } : + function(s) { this.buf += s; }, + fl: (useArrayBuffer) ? function() { var r = this.buf.join(''); this.buf = []; return r; } : + function() { var r = this.buf; this.buf = ''; return r; }, + + // lambda replace section + ls: function(val, ctx, partials, inverted, start, end, tags) { + var cx = ctx[ctx.length - 1], + t = null; + + if (!inverted && this.c && val.length > 0) { + return this.ho(val, cx, partials, this.text.substring(start, end), tags); + } + + t = val.call(cx); + + if (typeof t == 'function') { + if (inverted) { + return true; + } else if (this.c) { + return this.ho(t, cx, partials, this.text.substring(start, end), tags); + } + } + + return t; + }, + + // lambda replace variable + lv: function(val, ctx, partials) { + var cx = ctx[ctx.length - 1]; + var result = val.call(cx); + + if (typeof result == 'function') { + result = coerceToString(result.call(cx)); + if (this.c && ~result.indexOf("{\u007B")) { + return this.c.compile(result, this.options).render(cx, partials); + } + } + + return coerceToString(result); + } + + }; + + var rAmp = /&/g, + rLt = //g, + rApos =/\'/g, + rQuot = /\"/g, + hChars =/[&<>\"\']/; + + + function coerceToString(val) { + return String((val === null || val === undefined) ? '' : val); + } + + function hoganEscape(str) { + str = coerceToString(str); + return hChars.test(str) ? + str + .replace(rAmp,'&') + .replace(rLt,'<') + .replace(rGt,'>') + .replace(rApos,''') + .replace(rQuot, '"') : + str; + } + + var isArray = Array.isArray || function(a) { + return Object.prototype.toString.call(a) === '[object Array]'; + }; + +})(typeof exports !== 'undefined' ? exports : Hogan); + + + + +(function (Hogan) { + // Setup regex assignments + // remove whitespace according to Mustache spec + var rIsWhitespace = /\S/, + rQuot = /\"/g, + rNewline = /\n/g, + rCr = /\r/g, + rSlash = /\\/g, + tagTypes = { + '#': 1, '^': 2, '/': 3, '!': 4, '>': 5, + '<': 6, '=': 7, '_v': 8, '{': 9, '&': 10 + }; + + Hogan.scan = function scan(text, delimiters) { + var len = text.length, + IN_TEXT = 0, + IN_TAG_TYPE = 1, + IN_TAG = 2, + state = IN_TEXT, + tagType = null, + tag = null, + buf = '', + tokens = [], + seenTag = false, + i = 0, + lineStart = 0, + otag = '{{', + ctag = '}}'; + + function addBuf() { + if (buf.length > 0) { + tokens.push(new String(buf)); + buf = ''; + } + } + + function lineIsWhitespace() { + var isAllWhitespace = true; + for (var j = lineStart; j < tokens.length; j++) { + isAllWhitespace = + (tokens[j].tag && tagTypes[tokens[j].tag] < tagTypes['_v']) || + (!tokens[j].tag && tokens[j].match(rIsWhitespace) === null); + if (!isAllWhitespace) { + return false; + } + } + + return isAllWhitespace; + } + + function filterLine(haveSeenTag, noNewLine) { + addBuf(); + + if (haveSeenTag && lineIsWhitespace()) { + for (var j = lineStart, next; j < tokens.length; j++) { + if (!tokens[j].tag) { + if ((next = tokens[j+1]) && next.tag == '>') { + // set indent to token value + next.indent = tokens[j].toString() + } + tokens.splice(j, 1); + } + } + } else if (!noNewLine) { + tokens.push({tag:'\n'}); + } + + seenTag = false; + lineStart = tokens.length; + } + + function changeDelimiters(text, index) { + var close = '=' + ctag, + closeIndex = text.indexOf(close, index), + delimiters = trim( + text.substring(text.indexOf('=', index) + 1, closeIndex) + ).split(' '); + + otag = delimiters[0]; + ctag = delimiters[1]; + + return closeIndex + close.length - 1; + } + + if (delimiters) { + delimiters = delimiters.split(' '); + otag = delimiters[0]; + ctag = delimiters[1]; + } + + for (i = 0; i < len; i++) { + if (state == IN_TEXT) { + if (tagChange(otag, text, i)) { + --i; + addBuf(); + state = IN_TAG_TYPE; + } else { + if (text.charAt(i) == '\n') { + filterLine(seenTag); + } else { + buf += text.charAt(i); + } + } + } else if (state == IN_TAG_TYPE) { + i += otag.length - 1; + tag = tagTypes[text.charAt(i + 1)]; + tagType = tag ? text.charAt(i + 1) : '_v'; + if (tagType == '=') { + i = changeDelimiters(text, i); + state = IN_TEXT; + } else { + if (tag) { + i++; + } + state = IN_TAG; + } + seenTag = i; + } else { + if (tagChange(ctag, text, i)) { + tokens.push({tag: tagType, n: trim(buf), otag: otag, ctag: ctag, + i: (tagType == '/') ? seenTag - ctag.length : i + otag.length}); + buf = ''; + i += ctag.length - 1; + state = IN_TEXT; + if (tagType == '{') { + if (ctag == '}}') { + i++; + } else { + cleanTripleStache(tokens[tokens.length - 1]); + } + } + } else { + buf += text.charAt(i); + } + } + } + + filterLine(seenTag, true); + + return tokens; + } + + function cleanTripleStache(token) { + if (token.n.substr(token.n.length - 1) === '}') { + token.n = token.n.substring(0, token.n.length - 1); + } + } + + function trim(s) { + if (s.trim) { + return s.trim(); + } + + return s.replace(/^\s*|\s*$/g, ''); + } + + function tagChange(tag, text, index) { + if (text.charAt(index) != tag.charAt(0)) { + return false; + } + + for (var i = 1, l = tag.length; i < l; i++) { + if (text.charAt(index + i) != tag.charAt(i)) { + return false; + } + } + + return true; + } + + function buildTree(tokens, kind, stack, customTags) { + var instructions = [], + opener = null, + token = null; + + while (tokens.length > 0) { + token = tokens.shift(); + if (token.tag == '#' || token.tag == '^' || isOpener(token, customTags)) { + stack.push(token); + token.nodes = buildTree(tokens, token.tag, stack, customTags); + instructions.push(token); + } else if (token.tag == '/') { + if (stack.length === 0) { + throw new Error('Closing tag without opener: /' + token.n); + } + opener = stack.pop(); + if (token.n != opener.n && !isCloser(token.n, opener.n, customTags)) { + throw new Error('Nesting error: ' + opener.n + ' vs. ' + token.n); + } + opener.end = token.i; + return instructions; + } else { + instructions.push(token); + } + } + + if (stack.length > 0) { + throw new Error('missing closing tag: ' + stack.pop().n); + } + + return instructions; + } + + function isOpener(token, tags) { + for (var i = 0, l = tags.length; i < l; i++) { + if (tags[i].o == token.n) { + token.tag = '#'; + return true; + } + } + } + + function isCloser(close, open, tags) { + for (var i = 0, l = tags.length; i < l; i++) { + if (tags[i].c == close && tags[i].o == open) { + return true; + } + } + } + + Hogan.generate = function (tree, text, options) { + var code = 'var _=this;_.b(i=i||"");' + walk(tree) + 'return _.fl();'; + if (options.asString) { + return 'function(c,p,i){' + code + ';}'; + } + + return new Hogan.Template(new Function('c', 'p', 'i', code), text, Hogan, options); + } + + function esc(s) { + return s.replace(rSlash, '\\\\') + .replace(rQuot, '\\\"') + .replace(rNewline, '\\n') + .replace(rCr, '\\r'); + } + + function chooseMethod(s) { + return (~s.indexOf('.')) ? 'd' : 'f'; + } + + function walk(tree) { + var code = ''; + for (var i = 0, l = tree.length; i < l; i++) { + var tag = tree[i].tag; + if (tag == '#') { + code += section(tree[i].nodes, tree[i].n, chooseMethod(tree[i].n), + tree[i].i, tree[i].end, tree[i].otag + " " + tree[i].ctag); + } else if (tag == '^') { + code += invertedSection(tree[i].nodes, tree[i].n, + chooseMethod(tree[i].n)); + } else if (tag == '<' || tag == '>') { + code += partial(tree[i]); + } else if (tag == '{' || tag == '&') { + code += tripleStache(tree[i].n, chooseMethod(tree[i].n)); + } else if (tag == '\n') { + code += text('"\\n"' + (tree.length-1 == i ? '' : ' + i')); + } else if (tag == '_v') { + code += variable(tree[i].n, chooseMethod(tree[i].n)); + } else if (tag === undefined) { + code += text('"' + esc(tree[i]) + '"'); + } + } + return code; + } + + function section(nodes, id, method, start, end, tags) { + return 'if(_.s(_.' + method + '("' + esc(id) + '",c,p,1),' + + 'c,p,0,' + start + ',' + end + ',"' + tags + '")){' + + '_.rs(c,p,' + + 'function(c,p,_){' + + walk(nodes) + + '});c.pop();}'; + } + + function invertedSection(nodes, id, method) { + return 'if(!_.s(_.' + method + '("' + esc(id) + '",c,p,1),c,p,1,0,0,"")){' + + walk(nodes) + + '};'; + } + + function partial(tok) { + return '_.b(_.rp("' + esc(tok.n) + '",c,p,"' + (tok.indent || '') + '"));'; + } + + function tripleStache(id, method) { + return '_.b(_.t(_.' + method + '("' + esc(id) + '",c,p,0)));'; + } + + function variable(id, method) { + return '_.b(_.v(_.' + method + '("' + esc(id) + '",c,p,0)));'; + } + + function text(id) { + return '_.b(' + id + ');'; + } + + Hogan.parse = function(tokens, text, options) { + options = options || {}; + return buildTree(tokens, '', [], options.sectionTags || []); + }, + + Hogan.cache = {}; + + Hogan.compile = function(text, options) { + // options + // + // asString: false (default) + // + // sectionTags: [{o: '_foo', c: 'foo'}] + // An array of object with o and c fields that indicate names for custom + // section tags. The example above allows parsing of {{_foo}}{{/foo}}. + // + // delimiters: A string that overrides the default delimiters. + // Example: "<% %>" + // + options = options || {}; + + var key = text + '||' + !!options.asString; + + var t = this.cache[key]; + + if (t) { + return t; + } + + t = this.generate(this.parse(this.scan(text, options.delimiters), text, options), text, options); + return this.cache[key] = t; + }; +})(typeof exports !== 'undefined' ? exports : Hogan); +