| @@ -0,0 +1,20 @@ | ||
| dominate/__init__.py,sha256=qZ6CLTkIM6XUGeCvrWxODTC0KwHqdGHhuPK_IOq9FN8,88 | ||
| dominate/_version.py,sha256=sqVQ8fFGbnsGqRXKtmC4MtWqfJMePAbNboHtUAAf9RY,23 | ||
| dominate/document.py,sha256=wKkHjVd4YINjgtDUvtkdpbav2wq29P4ANBMqbAUgWK4,2217 | ||
| dominate/dom1core.py,sha256=abRAFVImeyRmdK_eLUE3Fr0LfnIGiRkJzfKZCfKJXqM,1760 | ||
| dominate/dom_tag.py,sha256=SBv1VUBipybwtviXRC87hoVqgyhOnrWDjJ9GmfjOTnw,12431 | ||
| dominate/tags.py,sha256=D2DNA0hKMIjxx7c7hcPxOO9STbxKv2bt6cw_LhljZ1A,28020 | ||
| dominate/util.py,sha256=8c6HBl5jc20v6L9CHO-xm9pGUXw7Voc1uk__udZksIY,3923 | ||
| dominate-2.1.16.dist-info/DESCRIPTION.rst,sha256=gWLL40AIFSAjK-NC9vpc2OiWiIsh4s8varhb3lkUNB0,10314 | ||
| dominate-2.1.16.dist-info/METADATA,sha256=ivsXUqbHK_MTJcVJHPWQPajeOFuun8KV6sSituFPPiY,11461 | ||
| dominate-2.1.16.dist-info/metadata.json,sha256=JxWYMTgjM3h_UgSmgWeNQ0ttMQB_M8hkIiXpezA3e5U,1249 | ||
| dominate-2.1.16.dist-info/RECORD,, | ||
| dominate-2.1.16.dist-info/top_level.txt,sha256=lkq2NuHoGLvvjfSl6OLHyLNBidBuRCf1CbUpKhH-lYY,9 | ||
| dominate-2.1.16.dist-info/WHEEL,sha256=54bVun1KfEBTJ68SHUmbxNPj80VxlQ0sHi4gZdGZXEY,92 | ||
| dominate/_version.pyc,, | ||
| dominate/util.pyc,, | ||
| dominate/dom_tag.pyc,, | ||
| dominate/__init__.pyc,, | ||
| dominate/document.pyc,, | ||
| dominate/tags.pyc,, | ||
| dominate/dom1core.pyc,, |
| @@ -0,0 +1,5 @@ | ||
| Wheel-Version: 1.0 | ||
| Generator: bdist_wheel (0.24.0) | ||
| Root-Is-Purelib: true | ||
| Tag: py2-none-any | ||
|
|
| @@ -0,0 +1 @@ | ||
| {"license": "LICENSE.txt", "name": "dominate", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API.", "version": "2.1.16", "extensions": {"python.details": {"project_urls": {"Home": "http://github.com/Knio/dominate/"}, "document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "tom@zkpq.ca", "name": "Tom Flanagan and Jake Wharton"}]}}, "keywords": ["framework", "templating", "template", "html", "xhtml", "python", "html5"], "classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Markup :: HTML"]} |
| @@ -0,0 +1 @@ | ||
| dominate |
| @@ -0,0 +1,4 @@ | ||
| from ._version import __version__ | ||
| version = __version__ | ||
|
|
||
| from .document import document |
| @@ -0,0 +1 @@ | ||
| __version__ = '2.1.16' |
| @@ -0,0 +1,75 @@ | ||
| __license__ = ''' | ||
| This file is part of Dominate. | ||
| Dominate is free software: you can redistribute it and/or modify | ||
| it under the terms of the GNU Lesser General Public License as | ||
| published by the Free Software Foundation, either version 3 of | ||
| the License, or (at your option) any later version. | ||
| Dominate is distributed in the hope that it will be useful, but | ||
| WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| GNU Lesser General Public License for more details. | ||
| You should have received a copy of the GNU Lesser General | ||
| Public License along with Dominate. If not, see | ||
| <http://www.gnu.org/licenses/>. | ||
| ''' | ||
|
|
||
| from . import tags | ||
|
|
||
| try: | ||
| basestring = basestring | ||
| except NameError: # py3 | ||
| basestring = str | ||
| unicode = str | ||
|
|
||
| class document(tags.html): | ||
| tagname = 'html' | ||
| def __init__(self, title='Dominate', doctype='<!DOCTYPE html>', request=None): | ||
| ''' | ||
| Creates a new document instance. Accepts `title`, `doctype`, and `request` keyword arguments. | ||
| ''' | ||
| super(document, self).__init__() | ||
| self.doctype = doctype | ||
| self.head = super(document, self).add(tags.head()) | ||
| self.body = super(document, self).add(tags.body()) | ||
| self.title_node = self.head.add(tags.title(title)) | ||
| self._entry = self.body | ||
|
|
||
| def get_title(self): | ||
| return self.title_node.text | ||
|
|
||
| def set_title(self, title): | ||
| if isinstance(title, basestring): | ||
| self.title_node.text = title | ||
| else: | ||
| self.head.remove(self.title_node) | ||
| self.head.add(title) | ||
| self.title_node = title | ||
|
|
||
| title = property(get_title, set_title) | ||
|
|
||
| def add(self, *args): | ||
| ''' | ||
| Adding tags to a document appends them to the <body>. | ||
| ''' | ||
| return self._entry.add(*args) | ||
|
|
||
| def render(self, *args, **kwargs): | ||
| ''' | ||
| Creates a <title> tag if not present and renders the DOCTYPE and tag tree. | ||
| ''' | ||
| r = [] | ||
|
|
||
| #Validates the tag tree and adds the doctype if one was set | ||
| if self.doctype: | ||
| r.append(self.doctype) | ||
| r.append('\n') | ||
| r.append(super(document, self).render(*args, **kwargs)) | ||
|
|
||
| return u''.join(r) | ||
| __str__ = __unicode__ = render | ||
|
|
||
| def __repr__(self): | ||
| return '<dominate.document "%s">' % self.title |
| @@ -0,0 +1,68 @@ | ||
| __license__ = ''' | ||
| This file is part of Dominate. | ||
| Dominate is free software: you can redistribute it and/or modify | ||
| it under the terms of the GNU Lesser General Public License as | ||
| published by the Free Software Foundation, either version 3 of | ||
| the License, or (at your option) any later version. | ||
| Dominate is distributed in the hope that it will be useful, but | ||
| WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| GNU Lesser General Public License for more details. | ||
| You should have received a copy of the GNU Lesser General | ||
| Public License along with Dominate. If not, see | ||
| <http://www.gnu.org/licenses/>. | ||
| ''' | ||
|
|
||
| try: | ||
| basestring = basestring | ||
| except NameError: # py3 | ||
| basestring = str | ||
| unicode = str | ||
|
|
||
|
|
||
| class dom1core(object): | ||
| ''' | ||
| Implements the Document Object Model (Core) Level 1 | ||
| http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001/ | ||
| http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001/level-one-core.html | ||
| ''' | ||
| @property | ||
| def parentNode(self): | ||
| ''' | ||
| DOM API: Returns the parent tag of the current element. | ||
| ''' | ||
| return self.parent | ||
|
|
||
| def getElementById(self, id): | ||
| ''' | ||
| DOM API: Returns single element with matching id value. | ||
| ''' | ||
| results = self.get(id=id) | ||
| if len(results) > 1: | ||
| raise ValueError('Multiple tags with id "%s".' % id) | ||
| elif results: | ||
| return results[0] | ||
| else: | ||
| return None | ||
|
|
||
| def getElementsByTagName(self, name): | ||
| ''' | ||
| DOM API: Returns all tags that match name. | ||
| ''' | ||
| if isinstance(name, basestring): | ||
| return self.get(name.lower()) | ||
| else: | ||
| return None | ||
|
|
||
| def appendChild(self, obj): | ||
| ''' | ||
| DOM API: Add an item to the end of the children list. | ||
| ''' | ||
| self.add(obj) | ||
| return self | ||
|
|
||
|
|
| @@ -0,0 +1,169 @@ | ||
| ''' | ||
| Utility classes for creating dynamic html documents | ||
| ''' | ||
|
|
||
| __license__ = ''' | ||
| This file is part of Dominate. | ||
| Dominate is free software: you can redistribute it and/or modify | ||
| it under the terms of the GNU Lesser General Public License as | ||
| published by the Free Software Foundation, either version 3 of | ||
| the License, or (at your option) any later version. | ||
| Dominate is distributed in the hope that it will be useful, but | ||
| WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| GNU Lesser General Public License for more details. | ||
| You should have received a copy of the GNU Lesser General | ||
| Public License along with Dominate. If not, see | ||
| <http://www.gnu.org/licenses/>. | ||
| ''' | ||
|
|
||
| import re | ||
| from .dom_tag import dom_tag | ||
|
|
||
|
|
||
| try: | ||
| basestring = basestring | ||
| except NameError: | ||
| basestring = str | ||
| unichr = chr | ||
|
|
||
| def include(f): | ||
| ''' | ||
| includes the contents of a file on disk. | ||
| takes a filename | ||
| ''' | ||
| fl = open(f, 'r') | ||
| data = fl.read() | ||
| fl.close() | ||
| return raw(data) | ||
|
|
||
|
|
||
| def system(cmd, data=None): | ||
| ''' | ||
| pipes the output of a program | ||
| ''' | ||
| import subprocess | ||
| s = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) | ||
| out, err = s.communicate(data) | ||
| return out.decode('utf8') | ||
|
|
||
|
|
||
| def escape(data, quote=True): # stoled from std lib cgi | ||
| ''' | ||
| Escapes special characters into their html entities | ||
| Replace special characters "&", "<" and ">" to HTML-safe sequences. | ||
| If the optional flag quote is true, the quotation mark character (") | ||
| is also translated. | ||
| This is used to escape content that appears in the body of an HTML cocument | ||
| ''' | ||
| data = data.replace("&", "&") # Must be done first! | ||
| data = data.replace("<", "<") | ||
| data = data.replace(">", ">") | ||
| if quote: | ||
| data = data.replace('"', """) | ||
| return data | ||
|
|
||
|
|
||
| _unescape = { | ||
| 'quot': 34, | ||
| 'amp': 38, | ||
| 'lt': 60, | ||
| 'gt': 62, | ||
| 'nbsp': 32, | ||
| # more here | ||
| # http://www.w3.org/TR/html4/sgml/entities.html | ||
| 'yuml': 255, | ||
| } | ||
|
|
||
|
|
||
| def unescape(data): | ||
| ''' | ||
| unescapes html entities. the opposite of escape. | ||
| ''' | ||
| cc = re.compile('&(?:(?:#(\d+))|([^;]+));') | ||
|
|
||
| result = [] | ||
| m = cc.search(data) | ||
| while m: | ||
| result.append(data[0:m.start()]) | ||
| d = m.group(1) | ||
| if d: | ||
| d = int(d) | ||
| result.append(unichr(d)) | ||
| else: | ||
| d = _unescape.get(m.group(2), ord('?')) | ||
| result.append(unichr(d)) | ||
|
|
||
| data = data[m.end():] | ||
| m = cc.search(data) | ||
|
|
||
| result.append(data) | ||
| return ''.join(result) | ||
|
|
||
|
|
||
| _reserved = ";/?:@&=+$, " | ||
| _replace_map = dict((c, '%%%2X' % ord(c)) for c in _reserved) | ||
|
|
||
|
|
||
| def url_escape(data): | ||
| return ''.join(_replace_map.get(c, c) for c in data) | ||
|
|
||
|
|
||
| def url_unescape(data): | ||
| return re.sub('%([0-9a-fA-F]{2})', | ||
| lambda m: unichr(int(m.group(1), 16)), data) | ||
|
|
||
|
|
||
| class lazy(dom_tag): | ||
| ''' | ||
| delays function execution until rendered | ||
| ''' | ||
| def __new__(_cls, *args, **kwargs): | ||
| ''' | ||
| Need to reset this special method or else | ||
| dom_tag will think it's being used as a dectorator. | ||
| This means lazy() can't be used as a dectorator, but | ||
| thinking about when you might want that just confuses me. | ||
| ''' | ||
| return object.__new__(_cls) | ||
|
|
||
| def __init__(self, func, *args, **kwargs): | ||
| super(lazy, self).__init__() | ||
| self.func = func | ||
| self.args = args | ||
| self.kwargs = kwargs | ||
|
|
||
|
|
||
| def _render(self, rendered, indent=1, inline=False): | ||
| r = self.func(*self.args, **self.kwargs) | ||
| rendered.append(str(r)) | ||
|
|
||
|
|
||
| # TODO rename this to raw? | ||
| class text(dom_tag): | ||
| ''' | ||
| Just a string. useful for inside context managers | ||
| ''' | ||
| is_pretty = False | ||
|
|
||
| def __init__(self, _text, escape=True): | ||
| super(text, self).__init__() | ||
| if escape: | ||
| self.text = globals()['escape'](_text) | ||
| else: | ||
| self.text = _text | ||
|
|
||
| def _render(self, rendered, indent, inline): | ||
| rendered.append(self.text) | ||
| return rendered | ||
|
|
||
| def raw(s): | ||
| ''' | ||
| Inserts a raw string into the DOM. Unsafe. | ||
| ''' | ||
| return text(s, escape=False) |
| @@ -0,0 +1,193 @@ | ||
| #!/usr/bin/env python | ||
| # coding=utf8 | ||
|
|
||
| import re | ||
|
|
||
| from flask import Blueprint, current_app, url_for | ||
|
|
||
| try: | ||
| from wtforms.fields import HiddenField | ||
| except ImportError: | ||
| def is_hidden_field_filter(field): | ||
| raise RuntimeError('WTForms is not installed.') | ||
| else: | ||
| def is_hidden_field_filter(field): | ||
| return isinstance(field, HiddenField) | ||
|
|
||
| from .forms import render_form | ||
|
|
||
|
|
||
| __version__ = '3.3.5.7' | ||
| BOOTSTRAP_VERSION = re.sub(r'^(\d+\.\d+\.\d+).*', r'\1', __version__) | ||
| JQUERY_VERSION = '1.11.3' | ||
| HTML5SHIV_VERSION = '3.7.2' | ||
| RESPONDJS_VERSION = '1.4.2' | ||
|
|
||
|
|
||
| class CDN(object): | ||
| """Base class for CDN objects.""" | ||
| def get_resource_url(self, filename): | ||
| """Return resource url for filename.""" | ||
| raise NotImplementedError | ||
|
|
||
|
|
||
| class StaticCDN(object): | ||
| """A CDN that serves content from the local application. | ||
| :param static_endpoint: Endpoint to use. | ||
| :param rev: If ``True``, honor ``BOOTSTRAP_QUERYSTRING_REVVING``. | ||
| """ | ||
| def __init__(self, static_endpoint='static', rev=False): | ||
| self.static_endpoint = static_endpoint | ||
| self.rev = rev | ||
|
|
||
| def get_resource_url(self, filename): | ||
| extra_args = {} | ||
|
|
||
| if self.rev and current_app.config['BOOTSTRAP_QUERYSTRING_REVVING']: | ||
| extra_args['bootstrap'] = __version__ | ||
|
|
||
| return url_for(self.static_endpoint, filename=filename, **extra_args) | ||
|
|
||
|
|
||
| class WebCDN(object): | ||
| """Serves files from the Web. | ||
| :param baseurl: The baseurl. Filenames are simply appended to this URL. | ||
| """ | ||
| def __init__(self, baseurl): | ||
| self.baseurl = baseurl | ||
|
|
||
| def get_resource_url(self, filename): | ||
| return self.baseurl + filename | ||
|
|
||
|
|
||
| class ConditionalCDN(object): | ||
| """Serves files from one CDN or another, depending on whether a | ||
| configuration value is set. | ||
| :param confvar: Configuration variable to use. | ||
| :param primary: CDN to use if the configuration variable is ``True``. | ||
| :param fallback: CDN to use otherwise. | ||
| """ | ||
| def __init__(self, confvar, primary, fallback): | ||
| self.confvar = confvar | ||
| self.primary = primary | ||
| self.fallback = fallback | ||
|
|
||
| def get_resource_url(self, filename): | ||
| if current_app.config[self.confvar]: | ||
| return self.primary.get_resource_url(filename) | ||
| return self.fallback.get_resource_url(filename) | ||
|
|
||
|
|
||
| def bootstrap_find_resource(filename, cdn, use_minified=None, local=True): | ||
| """Resource finding function, also available in templates. | ||
| Tries to find a resource, will force SSL depending on | ||
| ``BOOTSTRAP_CDN_FORCE_SSL`` settings. | ||
| :param filename: File to find a URL for. | ||
| :param cdn: Name of the CDN to use. | ||
| :param use_minified': If set to ``True``/``False``, use/don't use | ||
| minified. If ``None``, honors | ||
| ``BOOTSTRAP_USE_MINIFIED``. | ||
| :param local: If ``True``, uses the ``local``-CDN when | ||
| ``BOOTSTRAP_SERVE_LOCAL`` is enabled. If ``False``, uses | ||
| the ``static``-CDN instead. | ||
| :return: A URL. | ||
| """ | ||
| config = current_app.config | ||
|
|
||
| if None == use_minified: | ||
| use_minified = config['BOOTSTRAP_USE_MINIFIED'] | ||
|
|
||
| if use_minified: | ||
| filename = '%s.min.%s' % tuple(filename.rsplit('.', 1)) | ||
|
|
||
| cdns = current_app.extensions['bootstrap']['cdns'] | ||
| resource_url = cdns[cdn].get_resource_url(filename) | ||
|
|
||
| if resource_url.startswith('//') and config['BOOTSTRAP_CDN_FORCE_SSL']: | ||
| resource_url = 'https:%s' % resource_url | ||
|
|
||
| return resource_url | ||
|
|
||
|
|
||
| class Bootstrap(object): | ||
| def __init__(self, app=None): | ||
| if app is not None: | ||
| self.init_app(app) | ||
|
|
||
| def init_app(self, app): | ||
| app.config.setdefault('BOOTSTRAP_USE_MINIFIED', True) | ||
| app.config.setdefault('BOOTSTRAP_CDN_FORCE_SSL', False) | ||
|
|
||
| app.config.setdefault('BOOTSTRAP_QUERYSTRING_REVVING', True) | ||
| app.config.setdefault('BOOTSTRAP_SERVE_LOCAL', False) | ||
|
|
||
| app.config.setdefault('BOOTSTRAP_LOCAL_SUBDOMAIN', None) | ||
|
|
||
| blueprint = Blueprint( | ||
| 'bootstrap', | ||
| __name__, | ||
| template_folder='templates', | ||
| static_folder='static', | ||
| static_url_path=app.static_url_path + '/bootstrap', | ||
| subdomain=app.config['BOOTSTRAP_LOCAL_SUBDOMAIN']) | ||
|
|
||
| # add the form rendering template filter | ||
| blueprint.add_app_template_filter(render_form) | ||
|
|
||
| app.register_blueprint(blueprint) | ||
|
|
||
| app.jinja_env.globals['bootstrap_is_hidden_field'] =\ | ||
| is_hidden_field_filter | ||
| app.jinja_env.globals['bootstrap_find_resource'] =\ | ||
| bootstrap_find_resource | ||
|
|
||
| if not hasattr(app, 'extensions'): | ||
| app.extensions = {} | ||
|
|
||
| local = StaticCDN('bootstrap.static', rev=True) | ||
| static = StaticCDN() | ||
|
|
||
| def lwrap(cdn, primary=static): | ||
| return ConditionalCDN('BOOTSTRAP_SERVE_LOCAL', primary, cdn) | ||
|
|
||
| bootstrap = lwrap( | ||
| WebCDN('//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/%s/' | ||
| % BOOTSTRAP_VERSION), | ||
| local) | ||
|
|
||
| jquery = lwrap( | ||
| WebCDN('//cdnjs.cloudflare.com/ajax/libs/jquery/%s/' | ||
| % JQUERY_VERSION), | ||
| local) | ||
|
|
||
| html5shiv = lwrap( | ||
| WebCDN('//cdnjs.cloudflare.com/ajax/libs/html5shiv/%s/' | ||
| % HTML5SHIV_VERSION)) | ||
|
|
||
| respondjs = lwrap( | ||
| WebCDN('//cdnjs.cloudflare.com/ajax/libs/respond.js/%s/' | ||
| % RESPONDJS_VERSION)) | ||
|
|
||
| app.extensions['bootstrap'] = { | ||
| 'cdns': { | ||
| 'local': local, | ||
| 'static': static, | ||
| 'bootstrap': bootstrap, | ||
| 'jquery': jquery, | ||
| 'html5shiv': html5shiv, | ||
| 'respond.js': respondjs, | ||
| }, | ||
| } | ||
|
|
||
| # setup support for flask-nav | ||
| renderers = app.extensions.setdefault('nav_renderers', {}) | ||
| renderer_name = (__name__ + '.nav', 'BootstrapRenderer') | ||
| renderers['bootstrap'] = renderer_name | ||
|
|
||
| # make bootstrap the default renderer | ||
| renderers[None] = renderer_name |
| @@ -0,0 +1,149 @@ | ||
| from dominate import tags | ||
| from dominate.util import raw | ||
| from flask import current_app | ||
| from markupsafe import Markup | ||
| from visitor import Visitor | ||
|
|
||
|
|
||
| def render_form(form, **kwargs): | ||
| r = WTFormsRenderer(**kwargs) | ||
|
|
||
| return Markup(r.visit(form)) | ||
|
|
||
|
|
||
| class WTFormsRenderer(Visitor): | ||
| def __init__(self, | ||
| action='', | ||
| id=None, | ||
| method='post', | ||
| extra_classes=[], | ||
| role='form', | ||
| enctype=None): | ||
| self.action = action | ||
| self.form_id = id | ||
| self.method = method | ||
| self.extra_classes = extra_classes | ||
| self.role = role | ||
| self.enctype = enctype | ||
|
|
||
| def _visited_file_field(self): | ||
| if self._real_enctype is None: | ||
| self._real_enctype = u'multipart/form-data' | ||
|
|
||
| def _get_wrap(self, node, classes='form-group'): | ||
| # add required class, which strictly speaking isn't bootstrap, but | ||
| # a common enough customization | ||
| if node.flags.required: | ||
| classes += ' required' | ||
|
|
||
| div = tags.div(_class=classes) | ||
| if current_app.debug: | ||
| div.add(tags.comment(' Field: {} ({}) '.format( | ||
| node.name, node.__class__.__name__))) | ||
|
|
||
| return div | ||
|
|
||
| def _wrapped_input(self, node, | ||
| type='text', | ||
| classes=['form-control'], **kwargs): | ||
| wrap = self._get_wrap(node) | ||
| wrap.add(tags.label(node.label.text, _for=node.id)) | ||
| wrap.add(tags.input(type=type, _class=' '.join(classes), **kwargs)) | ||
|
|
||
| return wrap | ||
|
|
||
| def visit_BooleanField(self, node): | ||
| wrap = self._get_wrap(node, classes='checkbox') | ||
|
|
||
| label = wrap.add(tags.label(_for=node.id)) | ||
| label.add(tags.input(type='checkbox')) | ||
| label.add(node.label.text) | ||
|
|
||
| return wrap | ||
|
|
||
| def visit_DateField(self, node): | ||
| return self._wrapped_input(node, 'date') | ||
|
|
||
| def visit_DateTimeField(self, node): | ||
| return self._wrapped_input(node, 'datetime-local') | ||
|
|
||
| def visit_DecimalField(self, node): | ||
| # FIXME: if range-validator is present, add limits? | ||
| return self._wrapped_input(node, 'number') | ||
|
|
||
| def visit_EmailField(self, node): | ||
| # note: WTForms does not actually have an EmailField, this function | ||
| # is called by visit_TextField based on which validators are enabled | ||
| return self._wrapped_input(node, 'email') | ||
|
|
||
| def visit_Field(self, node): | ||
| # FIXME: add error class | ||
|
|
||
| wrap = self._get_wrap(node) | ||
|
|
||
| # add the label | ||
| wrap.add(tags.label(node.label.text, _for=node.id)) | ||
| wrap.add(raw(node())) | ||
|
|
||
| if node.description: | ||
| wrap.add(tags.p(node.description, _class='help-block')) | ||
|
|
||
| return wrap | ||
|
|
||
| def visit_FileField(self, node): | ||
| self._visited_file_field() | ||
| return self._wrapped_input(node, 'file', classes=[]) | ||
|
|
||
| def visit_FloatField(self, node): | ||
| # FIXME: if range-validator is present, add limits? | ||
| return self._wrapped_input(node, 'number') | ||
|
|
||
| def visit_Form(self, node): | ||
| form = tags.form(_class=' '.join(['form'] + self.extra_classes)) | ||
|
|
||
| if self.action: | ||
| form['action'] = self.action | ||
|
|
||
| if self.form_id: | ||
| form['id'] = self.form_id | ||
|
|
||
| if self.method: | ||
| form['method'] = self.method | ||
|
|
||
| # prepare enctype, this will be auto-updated by file fields if | ||
| # necessary | ||
| self._real_enctype = self.enctype | ||
|
|
||
| # render fields | ||
| for field in node: | ||
| elem = self.visit(field) | ||
| form.add(elem) | ||
|
|
||
| if self._real_enctype: | ||
| form['enctype'] = self._real_enctype | ||
|
|
||
| return form | ||
|
|
||
| def visit_HiddenField(self, node): | ||
| return raw(node()) | ||
|
|
||
| def visit_IntegerField(self, node): | ||
| # FIXME: if range-validator is present, add limits? | ||
| return self._wrapped_input(node, 'number', step=1) | ||
|
|
||
| def visit_PasswordField(self, node): | ||
| return self._wrapped_input(node, 'password') | ||
|
|
||
| def visit_SubmitField(self, node): | ||
| button = tags.button(node.label.text, | ||
| _class='btn btn-default', | ||
| type='submit') | ||
| return button | ||
|
|
||
| def visit_TextField(self, node): | ||
| for v in node.validators: | ||
| if v.__class__.__name__ == 'Email': | ||
| # render email fields differently | ||
| return self.visit_EmailField(node) | ||
|
|
||
| return self._wrapped_input(node, 'text') |
| @@ -0,0 +1,102 @@ | ||
| from hashlib import sha1 | ||
| from dominate import tags | ||
| from visitor import Visitor | ||
|
|
||
|
|
||
| class BootstrapRenderer(Visitor): | ||
| def __init__(self, html5=True, id=None): | ||
| self.html5 = html5 | ||
| self._in_dropdown = False | ||
| self.id = id | ||
|
|
||
| def visit_Navbar(self, node): | ||
| # create a navbar id that is somewhat fixed, but do not leak any | ||
| # information about memory contents to the outside | ||
| node_id = self.id or sha1(str(id(node)).encode()).hexdigest() | ||
|
|
||
| root = tags.nav() if self.html5 else tags.div(role='navigation') | ||
| root['class'] = 'navbar navbar-default' | ||
|
|
||
| cont = root.add(tags.div(_class='container-fluid')) | ||
|
|
||
| # collapse button | ||
| header = cont.add(tags.div(_class='navbar-header')) | ||
| btn = header.add(tags.button()) | ||
| btn['type'] = 'button' | ||
| btn['class'] = 'navbar-toggle collapsed' | ||
| btn['data-toggle'] = 'collapse' | ||
| btn['data-target'] = '#' + node_id | ||
| btn['aria-expanded'] = 'false' | ||
| btn['aria-controls'] = 'navbar' | ||
|
|
||
| btn.add(tags.span('Toggle navigation', _class='sr-only')) | ||
| btn.add(tags.span(_class='icon-bar')) | ||
| btn.add(tags.span(_class='icon-bar')) | ||
| btn.add(tags.span(_class='icon-bar')) | ||
|
|
||
| # title may also have a 'get_url()' method, in which case we render | ||
| # a brand-link | ||
| if node.title is not None: | ||
| if hasattr(node.title, 'get_url'): | ||
| header.add(tags.a(node.title.text, _class='navbar-brand', | ||
| href=node.title.get_url())) | ||
| else: | ||
| header.add(tags.span(node.title, _class='navbar-brand')) | ||
|
|
||
| bar = cont.add(tags.div( | ||
| _class='navbar-collapse collapse', | ||
| id=node_id, | ||
| )) | ||
| bar_list = bar.add(tags.ul(_class='nav navbar-nav')) | ||
|
|
||
| for item in node.items: | ||
| bar_list.add(self.visit(item)) | ||
|
|
||
| return root | ||
|
|
||
| def visit_Text(self, node): | ||
| if not self._in_dropdown: | ||
| return tags.p(node.text, _class='navbar-text') | ||
| return tags.li(node.text, _class='dropdown-header') | ||
|
|
||
| def visit_Link(self, node): | ||
| item = tags.li() | ||
| item.add(tags.a(node.text, href=node.get_url())) | ||
|
|
||
| return item | ||
|
|
||
| def visit_Separator(self, node): | ||
| if not self._in_dropdown: | ||
| raise RuntimeError('Cannot render separator outside Subgroup.') | ||
| return tags.li(role='separator', _class='divider') | ||
|
|
||
| def visit_Subgroup(self, node): | ||
| if not self._in_dropdown: | ||
| li = tags.li(_class='dropdown') | ||
| if node.active: | ||
| li['class'] = 'active' | ||
| a = li.add(tags.a(node.title, href='#', _class='dropdown-toggle')) | ||
| a['data-toggle'] = 'dropdown' | ||
| a['role'] = 'button' | ||
| a['aria-haspopup'] = 'true' | ||
| a['aria-expanded'] = 'false' | ||
| a.add(tags.span(_class='caret')) | ||
|
|
||
| ul = li.add(tags.ul(_class='dropdown-menu')) | ||
|
|
||
| self._in_dropdown = True | ||
| for item in node.items: | ||
| ul.add(self.visit(item)) | ||
| self._in_dropdown = False | ||
|
|
||
| return li | ||
| else: | ||
| raise RuntimeError('Cannot render nested Subgroups') | ||
|
|
||
| def visit_View(self, node): | ||
| item = tags.li() | ||
| item.add(tags.a(node.text, href=node.get_url(), title=node.text)) | ||
| if node.active: | ||
| item['class'] = 'active' | ||
|
|
||
| return item |
| @@ -0,0 +1,13 @@ | ||
| // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. | ||
| require('../../js/transition.js') | ||
| require('../../js/alert.js') | ||
| require('../../js/button.js') | ||
| require('../../js/carousel.js') | ||
| require('../../js/collapse.js') | ||
| require('../../js/dropdown.js') | ||
| require('../../js/modal.js') | ||
| require('../../js/tooltip.js') | ||
| require('../../js/popover.js') | ||
| require('../../js/scrollspy.js') | ||
| require('../../js/tab.js') | ||
| require('../../js/affix.js') |
| @@ -0,0 +1,34 @@ | ||
| {% block doc -%} | ||
| <!DOCTYPE html> | ||
| <html{% block html_attribs %}{% endblock html_attribs %}> | ||
| {%- block html %} | ||
| <head> | ||
| {%- block head %} | ||
| <title>{% block title %}{% endblock title %}</title> | ||
|
|
||
| {%- block metas %} | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| {%- endblock metas %} | ||
|
|
||
| {%- block styles %} | ||
| <!-- Bootstrap --> | ||
| <link href="{{bootstrap_find_resource('css/bootstrap.css', cdn='bootstrap')}}" rel="stylesheet"> | ||
| {%- endblock styles %} | ||
| {%- endblock head %} | ||
| </head> | ||
| <body{% block body_attribs %}{% endblock body_attribs %}> | ||
| {% block body -%} | ||
| {% block navbar %} | ||
| {%- endblock navbar %} | ||
| {% block content -%} | ||
| {%- endblock content %} | ||
|
|
||
| {% block scripts %} | ||
| <script src="{{bootstrap_find_resource('jquery.js', cdn='jquery')}}"></script> | ||
| <script src="{{bootstrap_find_resource('js/bootstrap.js', cdn='bootstrap')}}"></script> | ||
| {%- endblock scripts %} | ||
| {%- endblock body %} | ||
| </body> | ||
| {%- endblock html %} | ||
| </html> | ||
| {% endblock doc -%} |
| @@ -0,0 +1,6 @@ | ||
| {% macro ie8() %} | ||
| <!--[if lt IE 9]> | ||
| <script src="{{bootstrap_find_resource('html5shiv.js', cdn='html5shiv', local=False)}}"></script> | ||
| <script src="{{bootstrap_find_resource('respond.js', cdn='respond.js', local=False)}}"></script> | ||
| <![endif]--> | ||
| {% endmacro %} |
| @@ -0,0 +1,38 @@ | ||
| {% macro analytics(account) -%} | ||
| <script type="text/javascript"> | ||
| var _gaq = _gaq || []; | ||
| _gaq.push(['_setAccount', '{{account}}']); | ||
| _gaq.push(['_trackPageview']); | ||
|
|
||
| (function() { | ||
| var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; | ||
| ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; | ||
| var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); | ||
| })(); | ||
| </script> | ||
| {% endmacro %} | ||
|
|
||
| {% macro uanalytics(id, options='auto', domain=None) %} | ||
| {# The uanalytics macro currently contains a hack to support legacy code. | ||
| The old signature was ``uanalytics(id, domain)`` when domain was a required | ||
| parameter that was passed on to the ga() function. | ||
|
|
||
| To preserve old behavior, if options is not a dictionary, it is passed on | ||
| unchanged. The ``domain`` parameter is added to not break calls with named | ||
| parameters, it will override any other value for options. | ||
|
|
||
| More modern code can simply pass any desired option to the analytics | ||
| function as desired. | ||
| #} | ||
| {%- if domain != None %} | ||
| {%- set options = domain %} | ||
| {%- endif %} | ||
| <script> | ||
| (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | ||
| (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | ||
| m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | ||
| })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); | ||
| ga('create', {{id|tojson|safe}}, {{options|tojson|safe}}); | ||
| ga('send', 'pageview'); | ||
| </script> | ||
| {% endmacro %} |
| @@ -0,0 +1,52 @@ | ||
| {% macro _arg_url_for(endpoint, base) %} | ||
| {# calls url_for() with a given endpoint and **base as the parameters, | ||
| additionally passing on all keyword_arguments (may overwrite existing ones) | ||
| #} | ||
| {%- with kargs = base.copy(), | ||
| _ = kargs.update(kwargs) -%} | ||
| {{url_for(endpoint, **kargs)}} | ||
| {%- endwith %} | ||
| {%- endmacro %} | ||
|
|
||
| {% macro render_pagination(pagination, | ||
| endpoint=None, | ||
| prev=('«')|safe, | ||
| next=('»')|safe, | ||
| size=None, | ||
| ellipses='…', | ||
| args={} | ||
| ) | ||
| -%} | ||
| {# poor man's "do": #} | ||
| {% with url_args = {}, | ||
| _ = url_args.update(request.view_args if not endpoint else {}), | ||
| __ = url_args.update(request.args if not endpoint else {}), | ||
| ___ = url_args.update(args) %} | ||
| {% with endpoint = endpoint or request.endpoint %} | ||
| <nav> | ||
| <ul class="pagination{% if size %} pagination-{{size}}{% endif %}"{{kwargs|xmlattr}}> | ||
| {# prev and next are only show if a symbol has been passed. #} | ||
| {% if prev != None -%} | ||
| <li{% if not pagination.has_prev %} class="disabled"{% endif %}><a href="{{_arg_url_for(endpoint, url_args, page=pagination.prev_num) if pagination.has_prev else '#'}}">{{prev}}</li></a> | ||
| {%- endif -%} | ||
|
|
||
| {%- for page in pagination.iter_pages() %} | ||
| {% if page %} | ||
| {% if page != pagination.page %} | ||
| <li><a href="{{_arg_url_for(endpoint, url_args, page=page)}}">{{page}}</a></li> | ||
| {% else %} | ||
| <li class="active"><a href="#">{{page}} <span class="sr-only">(current)</span></a></li> | ||
| {% endif %} | ||
| {% elif ellipses != None %} | ||
| <li class="disabled"><a href="#">{{ellipses}}</a></li> | ||
| {% endif %} | ||
| {%- endfor %} | ||
|
|
||
| {% if next != None -%} | ||
| <li{% if not pagination.has_next %} class="disabled"{% endif %}><a href="{{_arg_url_for(endpoint, url_args, page=pagination.next_num) if pagination.has_next else '#'}}">{{next}}</li></a> | ||
| {%- endif -%} | ||
| </ul> | ||
| </nav> | ||
| {% endwith %} | ||
| {% endwith %} | ||
| {% endmacro %} |
| @@ -0,0 +1,45 @@ | ||
| {% macro flashed_messages(messages=None, container=True, transform={ | ||
| 'critical': 'danger', | ||
| 'error': 'danger', | ||
| 'info': 'info', | ||
| 'warning': 'warning', | ||
| 'debug': 'info', | ||
| 'notset': 'info', | ||
| 'message': 'info', | ||
| }, default_category=None, dismissible=False) -%} | ||
| {% with messages = messages or get_flashed_messages(with_categories=True) -%} | ||
| {% if messages -%} {# don't output anything if there are no messages #} | ||
|
|
||
| {% if container -%} | ||
| <!-- begin message block --> | ||
| <div class="container flashed-messages"> | ||
| <div class="row"> | ||
| <div class="col-md-12"> | ||
| {% endif -%} | ||
|
|
||
| {% for cat, msg in messages %} <div class="alert alert-{{transform.get(cat.lower(), default_category or cat)}}{% if dismissible %} alert-dismissible{% endif %}" role="alert"> | ||
| {% if dismissible %} <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>{% endif %} | ||
| {{msg}} | ||
| </div> | ||
| {%- endfor -%} | ||
|
|
||
| {% if container %} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <!-- end message block --> | ||
| {% endif -%} | ||
|
|
||
| {% endif -%} | ||
| {% endwith -%} | ||
| {% endmacro -%} | ||
|
|
||
|
|
||
| {% macro icon(type=None, extra_classes=[]) -%} | ||
| <span{{ ({'class': (['glyphicon', 'glyphicon-' + type] + extra_classes)|join(' ')})|xmlattr}}{{kwargs|xmlattr}}></span> | ||
| {%- endmacro %} | ||
|
|
||
|
|
||
| {% macro form_button(url, content, method='post', class='btn-link') -%} | ||
| <form style="display: inline;" action='{{url}}' method='{{method}}'><button class="{{class|safe}}">{{content}}</button></form> | ||
| {%- endmacro %} |
| @@ -0,0 +1,211 @@ | ||
| {% macro form_errors(form, hiddens=True) %} | ||
| {%- if form.errors %} | ||
| {%- for fieldname, errors in form.errors.items() %} | ||
| {%- if bootstrap_is_hidden_field(form[fieldname]) and hiddens or | ||
| not bootstrap_is_hidden_field(form[fieldname]) and hiddens != 'only' %} | ||
| {%- for error in errors %} | ||
| <p class="error">{{error}}</p> | ||
| {%- endfor %} | ||
| {%- endif %} | ||
| {%- endfor %} | ||
| {%- endif %} | ||
| {%- endmacro %} | ||
|
|
||
| {% macro _hz_form_wrap(horizontal_columns, form_type, add_group=False, required=False) %} | ||
| {% if form_type == "horizontal" %} | ||
| {% if add_group %}<div class="form-group{% if required %} required{% endif %}">{% endif %} | ||
| <div class="col-{{horizontal_columns[0]}}-offset-{{horizontal_columns[1]}} | ||
| col-{{horizontal_columns[0]}}-{{horizontal_columns[2]}} | ||
| "> | ||
| {% endif %} | ||
| {{caller()}} | ||
|
|
||
| {% if form_type == "horizontal" %} | ||
| {% if add_group %}</div>{% endif %} | ||
| </div> | ||
| {% endif %} | ||
| {% endmacro %} | ||
|
|
||
| {% macro form_field(field, | ||
| form_type="basic", | ||
| horizontal_columns=('lg', 2, 10), | ||
| button_map={}) %} | ||
|
|
||
| {# this is a workaround hack for the more straightforward-code of just passing required=required parameter. older versions of wtforms do not have | ||
| the necessary fix for required=False attributes, but will also not set the required flag in the first place. we skirt the issue using the code below #} | ||
| {% if field.flags.required and not required in kwargs %} | ||
| {% set kwargs = dict(required=True, **kwargs) %} | ||
| {% endif %} | ||
|
|
||
| {% if field.widget.input_type == 'checkbox' %} | ||
| {% call _hz_form_wrap(horizontal_columns, form_type, True, required=required) %} | ||
| <div class="checkbox"> | ||
| <label> | ||
| {{field()|safe}} {{field.label.text|safe}} | ||
| </label> | ||
| </div> | ||
| {% endcall %} | ||
| {%- elif field.type == 'RadioField' -%} | ||
| {# note: A cleaner solution would be rendering depending on the widget, | ||
| this is just a hack for now, until I can think of something better #} | ||
| {% call _hz_form_wrap(horizontal_columns, form_type, True, required=required) %} | ||
| {% for item in field -%} | ||
| <div class="radio"> | ||
| <label> | ||
| {{item|safe}} {{item.label.text|safe}} | ||
| </label> | ||
| </div> | ||
| {% endfor %} | ||
| {% endcall %} | ||
| {%- elif field.type == 'SubmitField' -%} | ||
| {# deal with jinja scoping issues? #} | ||
| {% set field_kwargs = kwargs %} | ||
|
|
||
| {# note: same issue as above - should check widget, not field type #} | ||
| {% call _hz_form_wrap(horizontal_columns, form_type, True, required=required) %} | ||
| {{field(class='btn btn-%s' % button_map.get(field.name, 'default'), | ||
| **field_kwargs)}} | ||
| {% endcall %} | ||
| {%- elif field.type == 'FormField' -%} | ||
| {# note: FormFields are tricky to get right and complex setups requiring | ||
| these are probably beyond the scope of what this macro tries to do. | ||
| the code below ensures that things don't break horribly if we run into | ||
| one, but does not try too hard to get things pretty. #} | ||
| <fieldset> | ||
| <legend>{{field.label}}</legend> | ||
| {%- for subfield in field %} | ||
| {% if not bootstrap_is_hidden_field(subfield) -%} | ||
| {{ form_field(subfield, | ||
| form_type=form_type, | ||
| horizontal_columns=horizontal_columns, | ||
| button_map=button_map) }} | ||
| {%- endif %} | ||
| {%- endfor %} | ||
| </fieldset> | ||
| {% else -%} | ||
| <div class="form-group {% if field.errors %} has-error{% endif -%} | ||
| {%- if field.flags.required %} required{% endif -%} | ||
| "> | ||
| {%- if form_type == "inline" %} | ||
| {{field.label(class="sr-only")|safe}} | ||
| {% if field.type == 'FileField' %} | ||
| {{field(**kwargs)|safe}} | ||
| {% else %} | ||
| {{field(class="form-control", **kwargs)|safe}} | ||
| {% endif %} | ||
| {% elif form_type == "horizontal" %} | ||
| {{field.label(class="control-label " + ( | ||
| " col-%s-%s" % horizontal_columns[0:2] | ||
| ))|safe}} | ||
| <div class=" col-{{horizontal_columns[0]}}-{{horizontal_columns[2]}}"> | ||
| {% if field.type == 'FileField' %} | ||
| {{field(**kwargs)|safe}} | ||
| {% else %} | ||
| {{field(class="form-control", **kwargs)|safe}} | ||
| {% endif %} | ||
| </div> | ||
| {%- if field.errors %} | ||
| {%- for error in field.errors %} | ||
| {% call _hz_form_wrap(horizontal_columns, form_type, required=required) %} | ||
| <p class="help-block">{{error}}</p> | ||
| {% endcall %} | ||
| {%- endfor %} | ||
| {%- elif field.description -%} | ||
| {% call _hz_form_wrap(horizontal_columns, form_type, required=required) %} | ||
| <p class="help-block">{{field.description|safe}}</p> | ||
| {% endcall %} | ||
| {%- endif %} | ||
| {%- else -%} | ||
| {{field.label(class="control-label")|safe}} | ||
| {% if field.type == 'FileField' %} | ||
| {{field(**kwargs)|safe}} | ||
| {% else %} | ||
| {{field(class="form-control", **kwargs)|safe}} | ||
| {% endif %} | ||
|
|
||
| {%- if field.errors %} | ||
| {%- for error in field.errors %} | ||
| <p class="help-block">{{error}}</p> | ||
| {%- endfor %} | ||
| {%- elif field.description -%} | ||
| <p class="help-block">{{field.description|safe}}</p> | ||
| {%- endif %} | ||
| {%- endif %} | ||
| </div> | ||
| {% endif %} | ||
| {% endmacro %} | ||
|
|
||
| {# valid form types are "basic", "inline" and "horizontal" #} | ||
| {% macro quick_form(form, | ||
| action="", | ||
| method="post", | ||
| extra_classes=None, | ||
| role="form", | ||
| form_type="basic", | ||
| horizontal_columns=('lg', 2, 10), | ||
| enctype=None, | ||
| button_map={}, | ||
| id="") %} | ||
| {#- | ||
| action="" is what we want, from http://www.ietf.org/rfc/rfc2396.txt: | ||
|
|
||
| 4.2. Same-document References | ||
|
|
||
| A URI reference that does not contain a URI is a reference to the | ||
| current document. In other words, an empty URI reference within a | ||
| document is interpreted as a reference to the start of that document, | ||
| and a reference containing only a fragment identifier is a reference | ||
| to the identified fragment of that document. Traversal of such a | ||
| reference should not result in an additional retrieval action. | ||
| However, if the URI reference occurs in a context that is always | ||
| intended to result in a new request, as in the case of HTML's FORM | ||
| element, then an empty URI reference represents the base URI of the | ||
| current document and should be replaced by that URI when transformed | ||
| into a request. | ||
|
|
||
| -#} | ||
| {#- if any file fields are inside the form and enctype is automatic, adjust | ||
| if file fields are found. could really use the equalto test of jinja2 | ||
| here, but latter is not available until 2.8 | ||
|
|
||
| warning: the code below is guaranteed to make you cry =( | ||
| #} | ||
| {%- set _enctype = [] %} | ||
| {%- if enctype is none -%} | ||
| {%- for field in form %} | ||
| {%- if field.type == 'FileField' %} | ||
| {#- for loops come with a fairly watertight scope, so this list-hack is | ||
| used to be able to set values outside of it #} | ||
| {%- set _ = _enctype.append('multipart/form-data') -%} | ||
| {%- endif %} | ||
| {%- endfor %} | ||
| {%- else %} | ||
| {% set _ = _enctype.append(enctype) %} | ||
| {%- endif %} | ||
| <form | ||
| {%- if action != None %} action="{{action}}"{% endif -%} | ||
| {%- if id %} id="{{id}}"{% endif -%} | ||
| {%- if method %} method="{{method}}"{% endif %} | ||
| class="form | ||
| {%- if extra_classes %} {{extra_classes}}{% endif -%} | ||
| {%- if form_type == "horizontal" %} form-horizontal | ||
| {%- elif form_type == "inline" %} form-inline | ||
| {%- endif -%} | ||
| " | ||
| {%- if _enctype[0] %} enctype="{{_enctype[0]}}"{% endif -%} | ||
| {%- if role %} role="{{role}}"{% endif -%} | ||
| > | ||
| {{ form.hidden_tag() }} | ||
| {{ form_errors(form, hiddens='only') }} | ||
|
|
||
| {%- for field in form %} | ||
| {% if not bootstrap_is_hidden_field(field) -%} | ||
| {{ form_field(field, | ||
| form_type=form_type, | ||
| horizontal_columns=horizontal_columns, | ||
| button_map=button_map) }} | ||
| {%- endif %} | ||
| {%- endfor %} | ||
|
|
||
| </form> | ||
| {%- endmacro %} |
| @@ -0,0 +1,93 @@ | ||
| visitor | ||
| ======= | ||
|
|
||
| A tiny library to facilitate `visitor | ||
| <https://en.wikipedia.org/wiki/Visitor_pattern>`_ implementation in Python | ||
| (which are slightly peculiar due to dynamic typing). In fact, it is so small, | ||
| you may just be better off copy & pasting the source straight into your | ||
| project... | ||
|
|
||
|
|
||
| Example use | ||
| ----------- | ||
|
|
||
| A simple JSON-encoder: | ||
|
|
||
| .. code-block:: python | ||
| from visitor import Visitor | ||
| class JSONEncoder(Visitor): | ||
| def __init__(self): | ||
| self.indent = 0 | ||
| def escape_str(self, s): | ||
| # note: this is not a good escape function, do not use this in | ||
| # production! | ||
| s = s.replace('\\', '\\\\') | ||
| s = s.replace('"', '\\"') | ||
| return '"' + s + '"' | ||
| def visit_list(self, node): | ||
| self.indent += 1 | ||
| s = '[\n' + ' ' * self.indent | ||
| s += (',\n' + ' ' * self.indent).join(self.visit(item) | ||
| for item in node) | ||
| self.indent -= 1 | ||
| s += '\n' + ' ' * self.indent + ']' | ||
| return s | ||
| def visit_str(self, node): | ||
| return self.escape_str(node) | ||
| def visit_int(self, node): | ||
| return str(node) | ||
| def visit_bool(self, node): | ||
| return 'true' if node else 'false' | ||
| def visit_dict(self, node): | ||
| self.indent += 1 | ||
| s = '{\n' + ' ' * self.indent | ||
| s += (',\n' + ' ' * self.indent).join( | ||
| '{}: {}'.format(self.escape_str(key), self.visit(value)) | ||
| for key, value in sorted(node.items()) | ||
| ) | ||
| self.indent -= 1 | ||
| s += '\n' + ' ' * self.indent + '}' | ||
| return s | ||
| data = [ | ||
| 'List', 'of', 42, 'items', True, { | ||
| 'sub1': 'some string', | ||
| 'sub2': { | ||
| 'sub2sub1': False, | ||
| 'sub2sub2': 123, | ||
| } | ||
| } | ||
| ] | ||
| print(JSONEncoder().visit(data)) | ||
| Output:: | ||
|
|
||
| [ | ||
| "List", | ||
| "of", | ||
| 42, | ||
| "items", | ||
| true, | ||
| { | ||
| "sub1": "some string", | ||
| "sub2": { | ||
| "sub2sub1": false, | ||
| "sub2sub2": 123 | ||
| } | ||
| } | ||
| ] | ||
|
|
||
|
|
| @@ -0,0 +1,105 @@ | ||
| Metadata-Version: 2.0 | ||
| Name: visitor | ||
| Version: 0.1.2 | ||
| Summary: A tiny pythonic visitor implementation. | ||
| Home-page: http://github.com/mbr/visitor | ||
| Author: Marc Brinkmann | ||
| Author-email: git@marcbrinkmann.de | ||
| License: MIT | ||
| Platform: UNKNOWN | ||
| Classifier: Programming Language :: Python :: 2 | ||
| Classifier: Programming Language :: Python :: 3 | ||
|
|
||
| visitor | ||
| ======= | ||
|
|
||
| A tiny library to facilitate `visitor | ||
| <https://en.wikipedia.org/wiki/Visitor_pattern>`_ implementation in Python | ||
| (which are slightly peculiar due to dynamic typing). In fact, it is so small, | ||
| you may just be better off copy & pasting the source straight into your | ||
| project... | ||
|
|
||
|
|
||
| Example use | ||
| ----------- | ||
|
|
||
| A simple JSON-encoder: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| from visitor import Visitor | ||
|
|
||
|
|
||
| class JSONEncoder(Visitor): | ||
| def __init__(self): | ||
| self.indent = 0 | ||
|
|
||
| def escape_str(self, s): | ||
| # note: this is not a good escape function, do not use this in | ||
| # production! | ||
| s = s.replace('\\', '\\\\') | ||
| s = s.replace('"', '\\"') | ||
| return '"' + s + '"' | ||
|
|
||
| def visit_list(self, node): | ||
| self.indent += 1 | ||
| s = '[\n' + ' ' * self.indent | ||
| s += (',\n' + ' ' * self.indent).join(self.visit(item) | ||
| for item in node) | ||
| self.indent -= 1 | ||
| s += '\n' + ' ' * self.indent + ']' | ||
| return s | ||
|
|
||
| def visit_str(self, node): | ||
| return self.escape_str(node) | ||
|
|
||
| def visit_int(self, node): | ||
| return str(node) | ||
|
|
||
| def visit_bool(self, node): | ||
| return 'true' if node else 'false' | ||
|
|
||
| def visit_dict(self, node): | ||
| self.indent += 1 | ||
| s = '{\n' + ' ' * self.indent | ||
| s += (',\n' + ' ' * self.indent).join( | ||
| '{}: {}'.format(self.escape_str(key), self.visit(value)) | ||
| for key, value in sorted(node.items()) | ||
| ) | ||
| self.indent -= 1 | ||
| s += '\n' + ' ' * self.indent + '}' | ||
| return s | ||
|
|
||
|
|
||
| data = [ | ||
| 'List', 'of', 42, 'items', True, { | ||
| 'sub1': 'some string', | ||
| 'sub2': { | ||
| 'sub2sub1': False, | ||
| 'sub2sub2': 123, | ||
| } | ||
| } | ||
| ] | ||
|
|
||
| print(JSONEncoder().visit(data)) | ||
|
|
||
|
|
||
|
|
||
| Output:: | ||
|
|
||
| [ | ||
| "List", | ||
| "of", | ||
| 42, | ||
| "items", | ||
| true, | ||
| { | ||
| "sub1": "some string", | ||
| "sub2": { | ||
| "sub2sub1": false, | ||
| "sub2sub2": 123 | ||
| } | ||
| } | ||
| ] | ||
|
|
||
|
|
| @@ -0,0 +1,8 @@ | ||
| visitor/__init__.py,sha256=2h5sJeSdoIF0uLLl-mryYjsHQaDMvynZNOeQGX8UoVs,2059 | ||
| visitor-0.1.2.dist-info/DESCRIPTION.rst,sha256=3HtZDldUOheikNO-yOgQ2zcDFzmt_sjZgo0kKzMEd9c,2178 | ||
| visitor-0.1.2.dist-info/METADATA,sha256=z6DwXQ3Cx05XhJC7rE_4l5L4e6w9p50Zyq6u_plaP3Y,2505 | ||
| visitor-0.1.2.dist-info/metadata.json,sha256=0G6YagIFCkwyzKE62Nmn_pkF7gM2lVuGFOsVuiPwb14,512 | ||
| visitor-0.1.2.dist-info/RECORD,, | ||
| visitor-0.1.2.dist-info/top_level.txt,sha256=YzMUOuikZNH_JrhCf_4ZZFsbtWgkdMoKlTJ4D0CvfP4,8 | ||
| visitor-0.1.2.dist-info/WHEEL,sha256=54bVun1KfEBTJ68SHUmbxNPj80VxlQ0sHi4gZdGZXEY,92 | ||
| visitor/__init__.pyc,, |
| @@ -0,0 +1,5 @@ | ||
| Wheel-Version: 1.0 | ||
| Generator: bdist_wheel (0.24.0) | ||
| Root-Is-Purelib: true | ||
| Tag: py2-none-any | ||
|
|
| @@ -0,0 +1 @@ | ||
| {"license": "MIT", "name": "visitor", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "A tiny pythonic visitor implementation.", "version": "0.1.2", "extensions": {"python.details": {"project_urls": {"Home": "http://github.com/mbr/visitor"}, "document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "git@marcbrinkmann.de", "name": "Marc Brinkmann"}]}}, "classifiers": ["Programming Language :: Python :: 2", "Programming Language :: Python :: 3"]} |
| @@ -0,0 +1 @@ | ||
| visitor |
| @@ -0,0 +1,47 @@ | ||
| # Copyright (c) 2015 Marc Brinkmann | ||
|
|
||
| # Permission is hereby granted, free of charge, to any person obtaining a | ||
| # copy of this software and associated documentation files (the "Software"), | ||
| # to deal in the Software without restriction, including without limitation | ||
| # the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
| # and/or sell copies of the Software, and to permit persons to whom the | ||
| # Software is furnished to do so, subject to the following conditions: | ||
|
|
||
| # The above copyright notice and this permission notice shall be included in | ||
| # all copies or substantial portions of the Software. | ||
|
|
||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
| # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
| # DEALINGS IN THE SOFTWARE. | ||
|
|
||
|
|
||
| class Visitor(object): | ||
| """Base class for visitors.""" | ||
|
|
||
| def visit(self, node): | ||
| """Visit a node. | ||
| Calls ``visit_CLASSNAME`` on itself passing ``node``, where | ||
| ``CLASSNAME`` is the node's class. If the visitor does not implement an | ||
| appropriate visitation method, will go up the | ||
| `MRO <https://www.python.org/download/releases/2.3/mro/>`_ until a | ||
| match is found. | ||
| If the search exhausts all classes of node, raises a | ||
| :class:`~exceptions.NotImplementedError`. | ||
| :param node: The node to visit. | ||
| :return: The return value of the called visitation function. | ||
| """ | ||
| for cls in type(node).mro(): | ||
| meth = getattr(self, 'visit_' + cls.__name__, None) | ||
| if meth is None: | ||
| continue | ||
| return meth(node) | ||
|
|
||
| raise NotImplementedError('No visitation method visit_{}' | ||
| .format(node.__class__.__name__)) |