[Python-modules-commits] [yattag] 01/03: import yattag_1.5.2.orig.tar.gz
Sandro Tosi
morph at moszumanska.debian.org
Tue Apr 5 20:11:02 UTC 2016
This is an automated email from the git hooks/post-receive script.
morph pushed a commit to branch master
in repository yattag.
commit b8851c8c25384977b02641f098bed931ff39486b
Author: Sandro Tosi <morph at debian.org>
Date: Tue Apr 5 21:09:21 2016 +0100
import yattag_1.5.2.orig.tar.gz
---
PKG-INFO | 119 ++++++++++++++
README.rst | 92 +++++++++++
setup.py | 36 +++++
yattag/__init__.py | 62 ++++++++
yattag/doc.py | 432 ++++++++++++++++++++++++++++++++++++++++++++++++++
yattag/indentation.py | 308 +++++++++++++++++++++++++++++++++++
yattag/simpledoc.py | 321 +++++++++++++++++++++++++++++++++++++
7 files changed, 1370 insertions(+)
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..ce72073
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,119 @@
+Metadata-Version: 1.1
+Name: yattag
+Version: 1.5.2
+Summary: Generate HTML or XML in a pythonic way. Pure python alternative to web template engines.Can fill HTML forms with default values and error messages.
+Home-page: http://www.yattag.org
+Author: Benjamin Le Forestier
+Author-email: benjamin at leforestier.org
+License: UNKNOWN
+Description: Why use a template engine when you can generate HTML or XML documents with Python in a very readable way?
+
+ ( full tutorial on yattag.org_ )
+
+ Basic example
+ -------------
+
+ Nested html tags, no need to close tags.
+
+ .. code:: python
+
+ from yattag import Doc
+
+ doc, tag, text = Doc().tagtext()
+
+ with tag('html'):
+ with tag('body', id = 'hello'):
+ with tag('h1'):
+ text('Hello world!')
+
+ print(doc.getvalue())
+
+
+ Html form rendering
+ -------------------
+
+ Yattag can fill your HTML forms with default values and error messages.
+ Pass a ``defaults`` dictionnary of default values, and an ``errors`` dictionnary of error messages to the ``Doc`` constructor.
+ Then, use the special ``input``, ``textarea``, ``select``, ``option`` methods when generating your documents.
+
+
+ Example with default values
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ .. code:: python
+
+ from yattag import Doc
+
+ doc, tag, text = Doc(
+ defaults = {'ingredient': ['chocolate', 'coffee']}
+ ).tagtext()
+
+ with tag('form', action = ""):
+ with tag('label'):
+ text("Select one or more ingredients")
+ with doc.select(name = 'ingredient', multiple = "multiple"):
+ for value, description in (
+ ("chocolate", "Dark chocolate"),
+ ("almonds", "Roasted almonds"),
+ ("honey", "Acacia honey"),
+ ("coffee", "Ethiopian coffee")
+ ):
+ with doc.option(value = value):
+ text(description)
+ doc.stag('input', type = "submit", value = "Validate")
+
+ print(doc.getvalue())
+
+ Example with default values and errors
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ .. code:: python
+
+ from yattag import Doc
+
+ doc, tag, text = Doc(
+ defaults = {
+ 'title': 'Untitled',
+ 'contact_message': 'You just won the lottery!'
+ },
+ errors = {
+ 'contact_message': 'Your message looks like spam.'
+ }
+ ).tagtext()
+
+ with tag('h1'):
+ text('Contact form')
+ with tag('form', action = ""):
+ doc.input(name = 'title', type = 'text')
+ with doc.textarea(name = 'contact_message'):
+ pass
+ doc.stag('input', type = 'submit', value = 'Send my message')
+
+ print(doc.getvalue())
+
+ Full tutorial on yattag.org_
+
+ GitHub repo: https://github.com/leforestier/yattag
+
+ .. _yattag.org: http://www.yattag.org
+
+
+
+Keywords: html,template,templating,xml,document,form,rendering
+Platform: UNKNOWN
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Topic :: Internet :: WWW/HTTP
+Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..1571ab8
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,92 @@
+Why use a template engine when you can generate HTML or XML documents with Python in a very readable way?
+
+( full tutorial on yattag.org_ )
+
+Basic example
+-------------
+
+Nested html tags, no need to close tags.
+
+.. code:: python
+
+ from yattag import Doc
+
+ doc, tag, text = Doc().tagtext()
+
+ with tag('html'):
+ with tag('body', id = 'hello'):
+ with tag('h1'):
+ text('Hello world!')
+
+ print(doc.getvalue())
+
+
+Html form rendering
+-------------------
+
+Yattag can fill your HTML forms with default values and error messages.
+Pass a ``defaults`` dictionnary of default values, and an ``errors`` dictionnary of error messages to the ``Doc`` constructor.
+Then, use the special ``input``, ``textarea``, ``select``, ``option`` methods when generating your documents.
+
+
+Example with default values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code:: python
+
+ from yattag import Doc
+
+ doc, tag, text = Doc(
+ defaults = {'ingredient': ['chocolate', 'coffee']}
+ ).tagtext()
+
+ with tag('form', action = ""):
+ with tag('label'):
+ text("Select one or more ingredients")
+ with doc.select(name = 'ingredient', multiple = "multiple"):
+ for value, description in (
+ ("chocolate", "Dark chocolate"),
+ ("almonds", "Roasted almonds"),
+ ("honey", "Acacia honey"),
+ ("coffee", "Ethiopian coffee")
+ ):
+ with doc.option(value = value):
+ text(description)
+ doc.stag('input', type = "submit", value = "Validate")
+
+ print(doc.getvalue())
+
+Example with default values and errors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code:: python
+
+ from yattag import Doc
+
+ doc, tag, text = Doc(
+ defaults = {
+ 'title': 'Untitled',
+ 'contact_message': 'You just won the lottery!'
+ },
+ errors = {
+ 'contact_message': 'Your message looks like spam.'
+ }
+ ).tagtext()
+
+ with tag('h1'):
+ text('Contact form')
+ with tag('form', action = ""):
+ doc.input(name = 'title', type = 'text')
+ with doc.textarea(name = 'contact_message'):
+ pass
+ doc.stag('input', type = 'submit', value = 'Send my message')
+
+ print(doc.getvalue())
+
+Full tutorial on yattag.org_
+
+GitHub repo: https://github.com/leforestier/yattag
+
+.. _yattag.org: http://www.yattag.org
+
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..43b77b5
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,36 @@
+from distutils.core import setup
+
+with open('README.rst') as fd:
+ long_description = fd.read()
+
+setup(
+ name='yattag',
+ version='1.5.2',
+ packages=['yattag'],
+ author = 'Benjamin Le Forestier',
+ author_email = 'benjamin at leforestier.org',
+ url = 'http://www.yattag.org',
+ keywords = ["html", "template", "templating", "xml", "document", "form", "rendering"],
+ description = """\
+Generate HTML or XML in a pythonic way. Pure python alternative to web template engines.\
+Can fill HTML forms with default values and error messages.""",
+ long_description = long_description,
+ classifiers = [
+ 'Environment :: Web Environment',
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.2',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
+ 'Programming Language :: Python :: 3.5',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ ]
+)
diff --git a/yattag/__init__.py b/yattag/__init__.py
new file mode 100644
index 0000000..d81621e
--- /dev/null
+++ b/yattag/__init__.py
@@ -0,0 +1,62 @@
+"""
+The SimpleDoc class generates xml or html documents using context managers (`with` blocks).
+The Doc class extends the SimpleDoc class. It adds the capability to render
+html forms with defaults values and errors.
+These two classes can be used to define html templates in a web application.
+
+Basic example
+-------------
+
+Nested html tags, no need to close tags.
+
+.. code:: python
+
+ from yattag import Doc
+
+ doc, tag, text = Doc().tagtext()
+
+ with tag('html'):
+ with tag('body', id = 'hello'):
+ with tag('h1'):
+ text('Hello world!')
+
+ print(doc.getvalue())
+
+Html form rendering example with default values
+-----------------------------------------------
+
+.. code:: python
+
+ from yattag import Doc
+
+ doc, tag, text = Doc(
+ defaults = {'ingredient': ['chocolate', 'coffee']}
+ ).tagtext()
+
+ with tag('form', action = ""):
+ with tag('label'):
+ text("Select one or more ingredients")
+ with doc.select(name = 'ingredient', multiple = "multiple"):
+ for value, description in (
+ ("chocolate", "Dark chocolate"),
+ ("almonds", "Roasted almonds"),
+ ("honey", "Acacia honey"),
+ ("coffee", "Ethiopian coffee")
+ ):
+ with doc.option(value = value):
+ text(description)
+ doc.stag('input', type = "submit", value = "Validate")
+
+ print(doc.getvalue())
+
+Full tutorial on yattag.org_
+
+.. _yattag.org: http://www.yattag.org
+"""
+
+__author__ = "Benjamin Le Forestier (benjamin at leforestier.org)"
+__version__ = '1.5.2'
+
+from yattag.simpledoc import SimpleDoc
+from yattag.doc import Doc
+from yattag.indentation import indent
diff --git a/yattag/doc.py b/yattag/doc.py
new file mode 100644
index 0000000..fd09f50
--- /dev/null
+++ b/yattag/doc.py
@@ -0,0 +1,432 @@
+from yattag.simpledoc import dict_to_attrs, html_escape, attr_escape, SimpleDoc, DocError
+
+try:
+ range = xrange # for Python 2/3 compatibility
+except NameError:
+ pass
+
+__all__ = ['Doc']
+
+class SimpleInput(object):
+
+ """
+ class representing text inputs, password inputs, hidden inputs etc...
+ """
+
+ def __init__(self, name, tpe, attrs):
+ self.name = name
+ self.tpe = tpe
+ self.attrs = attrs
+
+ def render(self, defaults, errors, error_wrapper):
+ lst = []
+ attrs = dict(self.attrs)
+ error = errors and self.name in errors
+ if error:
+ if 'class' in attrs:
+ attrs['class'] = attrs['class'] + " error"
+ else:
+ attrs['class'] = "error"
+ lst.append(error_wrapper[0])
+ lst.append(html_escape(errors[self.name]))
+ lst.append(error_wrapper[1])
+
+ if self.name in defaults:
+ attrs['value'] = str(defaults[self.name])
+ attrs['name'] = self.name
+ lst.append('<input type="%s" %s />' % (self.tpe, dict_to_attrs(attrs)))
+
+ return ''.join(lst)
+
+
+class CheckableInput(object):
+ tpe = 'checkbox'
+
+ def __init__(self, name, attrs):
+ self.name = name
+ self.rank = 0
+ self.attrs = attrs
+
+ def setrank(self, n):
+ self.rank = n
+
+ def render(self, defaults, errors, error_wrapper):
+ lst = []
+ attrs = dict(self.attrs)
+ if self.rank == 0:
+ if errors:
+ if self.name in errors:
+ lst.append(error_wrapper[0])
+ lst.append(html_escape(errors[self.name]))
+ lst.append(error_wrapper[1])
+ if 'class' not in attrs:
+ attrs['class'] = "error"
+
+ if self.name in defaults and 'value' in self.attrs and defaults[self.name] == self.attrs['value']:
+ attrs['checked'] = 'checked'
+
+ attrs['name'] = self.name
+
+ lst.append('<input type="%s" %s />' % (self.__class__.tpe, dict_to_attrs(attrs)))
+
+ return ''.join(lst)
+
+
+class CheckboxInput(CheckableInput):
+ pass
+
+class RadioInput(CheckableInput):
+ tpe = 'radio'
+
+
+def groupclass(inputclass):
+
+ class InputGroup(object):
+
+ def __init__(self, name):
+ self.name = name
+ self.n_items = 0
+
+ def input(self, attrs):
+ input_instance = inputclass(self.name, attrs)
+ input_instance.setrank(self.n_items)
+ self.n_items += 1
+ return input_instance
+
+ return InputGroup
+
+class ContainerTag(object):
+
+ tag_name = 'textarea'
+
+ def __init__(self, name, attrs):
+ self.name = name
+ self.attrs = attrs
+
+ def render(self, defaults, errors, error_wrapper, inner_content = ''):
+ lst = []
+ attrs = dict(self.attrs)
+ if errors:
+ if self.name in errors:
+ lst.append(error_wrapper[0])
+ lst.append(html_escape(errors[self.name]))
+ lst.append(error_wrapper[1])
+ if 'class' not in attrs:
+ attrs['class'] = "error"
+ attrs['name'] = self.name
+
+ lst.append('<%s %s>' % (self.__class__.tag_name, dict_to_attrs(attrs)))
+ if self.name in defaults:
+ lst.append(html_escape(str(defaults[self.name])))
+ else:
+ lst.append(inner_content)
+
+ lst.append('</%s>' % self.__class__.tag_name)
+
+ return ''.join(lst)
+
+class Textarea(ContainerTag):
+ pass
+
+class Select(ContainerTag):
+ tag_name = 'select'
+
+
+class Option(object):
+ def __init__(self, name, multiple, value, attrs):
+ self.name = name
+ self.multiple = multiple
+ self.value = value
+ self.attrs = attrs
+
+ def render(self, defaults, errors, inner_content):
+ selected = ''
+ if self.name in defaults:
+ if self.multiple:
+ if self.value in defaults[self.name]:
+ selected = True
+ else:
+ if self.value == defaults[self.name]:
+ selected = True
+ lst = ['<option value="', attr_escape(self.value), '"']
+ if selected:
+ lst.append(' selected="selected"')
+ if self.attrs:
+ lst.append(' ')
+ lst.append(dict_to_attrs(self.attrs))
+ lst.append('>')
+ lst.append(inner_content)
+ lst.append('</option>')
+ return ''.join(lst)
+
+def _attrs_from_args(required_keys, *args, **kwargs):
+ # need to do all this to allow specifying attributes as (key, value) pairs
+ # while maintaining backward compatibility with previous versions
+ # of yattag, which allowed 'name', 'type', and 'value' attributes
+ # as positional or as keyword arguments
+ def raise_exception(arg):
+ raise ValueError(
+ "Optional attributes should be passed as (key, value) pairs or as keyword arguments."
+ "Got %s (type %s)" % (repr(arg), repr(type(arg)))
+ )
+ limit = 0
+ for arg in args:
+ if isinstance(arg, tuple):
+ break
+ else:
+ limit += 1
+ if limit > len(required_keys):
+ raise_exception(args[limit-1])
+ attrs = dict(zip(required_keys[:limit],args[:limit]))
+ for arg in args[limit:]:
+ if isinstance(arg, tuple):
+ attrs[arg[0]] = arg[1]
+ else:
+ raise_exception(arg)
+ attrs.update(
+ (('class', value) if key == 'klass' else (key, value))
+ for key, value in kwargs.items()
+ )
+
+ required_attrs = []
+
+ for key in required_keys:
+ try:
+ required_attrs.append(attrs.pop(key))
+ except KeyError:
+ raise ValueError(
+ "the %s attribute is missing" % repr(key)
+ )
+ return required_attrs + [attrs]
+
+class Doc(SimpleDoc):
+ """
+ The Doc class extends the SimpleDoc class with form rendering capabilities.
+ Pass default values or errors as dictionnaries to the Doc constructor, and
+ use the `input`, `textarea`, `select`, `option` methods
+ to append form elements to the document.
+ """
+
+ SimpleInput = SimpleInput
+ CheckboxInput = CheckboxInput
+ RadioInput = RadioInput
+ Textarea = Textarea
+ Select = Select
+ Option = Option
+
+ class TextareaTag(object):
+ def __init__(self, doc, name, attrs):
+ # name is the name attribute of the textarea, ex: 'contact_message'
+ # for <textarea name="contact_message">
+ self.doc = doc
+ self.name = name
+ self.attrs = attrs
+
+ def __enter__(self):
+ self.parent_tag = self.doc.current_tag
+ self.doc.current_tag = self
+ self.position = len(self.doc.result)
+ self.doc._append('')
+
+ def __exit__(self, tpe, value, traceback):
+ if value is None:
+ inner_content = ''.join(self.doc.result[self.position+1:])
+ del self.doc.result[self.position+1:]
+ rendered_textarea = self.doc.__class__.Textarea(self.name, self.attrs).render(
+ defaults = self.doc.defaults,
+ errors = self.doc.errors,
+ inner_content = inner_content,
+ error_wrapper = self.doc.error_wrapper
+ )
+ self.doc.result[self.position] = rendered_textarea
+ self.doc.current_tag = self.parent_tag
+
+
+ class SelectTag(object):
+ def __init__(self, doc, name, attrs):
+ # name is the name attribute of the select, ex: 'color'
+ # for <select name="color">
+ self.doc = doc
+ self.name = name
+ self.attrs = attrs
+ self.multiple = bool(attrs.get('multiple'))
+ self.old_current_select = None
+
+ def __enter__(self):
+ self.parent_tag = self.doc.current_tag
+ self.doc.current_tag = self
+ self.position = len(self.doc.result)
+ self.doc._append('')
+ self.old_current_select = self.doc.current_select
+ self.doc.current_select = self
+
+ def __exit__(self, tpe, value, traceback):
+ if value is None:
+ inner_content = ''.join(self.doc.result[self.position+1:])
+ del self.doc.result[self.position+1:]
+ rendered_select = self.doc.__class__.Select(self.name, self.attrs).render(
+ defaults = {}, # no defaults for the <select> tag. Defaults are handled by the <option> tags directly.
+ errors = self.doc.errors,
+ inner_content = inner_content,
+ error_wrapper = self.doc.error_wrapper
+ )
+ self.doc.result[self.position] = rendered_select
+ self.doc.current_tag = self.parent_tag
+ self.doc.current_select = self.old_current_select
+
+
+ class OptionTag(object):
+ def __init__(self, doc, select, value, attrs):
+ self.doc = doc
+ self.select = select
+ self.attrs = attrs
+ self.value = value
+
+ def __enter__(self):
+ self.parent_tag = self.doc.current_tag
+ self.doc.current_tag = self
+ self.position = len(self.doc.result)
+ self.doc._append('')
+
+ def __exit__(self, tpe, value, traceback):
+ if value is None:
+ inner_content = ''.join(self.doc.result[self.position+1:])
+ del self.doc.result[self.position+1:]
+ self.doc.result[self.position] = self.doc.__class__.Option(
+ name = self.select.name,
+ multiple = self.select.multiple,
+ value = self.value,
+ attrs = self.attrs
+ ).render(
+ defaults = self.doc.defaults,
+ errors = self.doc.errors,
+ inner_content = inner_content
+ )
+ self.doc.current_tag = self.parent_tag
+
+
+ def __init__(self, defaults = None, errors = None,
+ error_wrapper = ('<span class="error">', '</span>'), *args, **kwargs):
+ """
+ creates a Doc instance
+
+ defaults::
+ optional dictionnary of values used to fill html forms
+ errors::
+ optional dictionnary of errors used to fill html forms
+
+ Example 1::
+ doc = Doc()
+
+ Example 2::
+ doc = Doc(
+ defaults = {
+ 'beverage': 'coffee',
+ 'preferences': ['milk', 'sugar'],
+ 'use_discount': True
+ },
+ errors = {
+ 'preferences': "We ran out of milk!"
+ }
+ )
+
+ Note: very often you'll want to call the `tagtext` method just after
+ creating a Doc instance. Like this::
+
+ doc, tag, text = Doc(defaults = {'color': 'blue'}).tagtext()
+
+ This way, you can write `tag` (resp. `text`) in place of `doc.tag`
+ (resp. `doc.text`). When writing long html templates or xml documents,
+ it's a gain in readability and performance.
+ """
+
+ super(Doc, self).__init__(*args, **kwargs)
+ self.defaults = defaults or {}
+ self.errors = errors or {}
+ self.error_wrapper = error_wrapper
+ self.radios = {}
+ self.checkboxes = {}
+ self.current_select = None
+ self.radio_group_class = groupclass(self.__class__.RadioInput)
+ self.checkbox_group_class = groupclass(self.__class__.CheckboxInput)
+ self._fields = set()
+ self._detached_errors_pos = []
+
+
+ def input(self, *args, **kwargs):
+ "required attributes: 'name' and 'type'"
+ name, type, attrs = _attrs_from_args(('name', 'type'), *args, **kwargs)
+ self._fields.add(name)
+ if type in (
+ 'text', 'password', 'hidden', 'search', 'email', 'url', 'number',
+ 'range', 'date', 'datetime', 'datetime-local', 'month', 'week',
+ 'time', 'color'
+ ):
+ self.asis(
+ self.__class__.SimpleInput(name, type, attrs).render(
+ self.defaults, self.errors, self.error_wrapper
+ )
+ )
+ return
+ if type == 'radio':
+ if name not in self.radios:
+ self.radios[name] = self.radio_group_class(name)
+ checkable_group = self.radios[name]
+ elif type == 'checkbox':
+ if name not in self.checkboxes:
+ self.checkboxes[name] = self.checkbox_group_class(name)
+ checkable_group = self.checkboxes[name]
+ else:
+ if type == 'submit':
+ raise DocError("Unhandled input type: submit. Use doc.stag('input', type = 'submit', value='whatever') instead.")
+ else:
+ raise DocError("Unknown input type: %s" % type)
+
+ self._append(checkable_group.input(attrs).render(self.defaults,self.errors, self.error_wrapper))
+
+ def textarea(self, *args, **kwargs):
+ "required attribute: 'name'"
+ name, attrs = _attrs_from_args(('name',), *args, **kwargs)
+ self._fields.add(name)
+ return self.__class__.TextareaTag(self, name, attrs)
+
+ def select(self, *args, **kwargs):
+ "required attribute: 'name'"
+ name, attrs = _attrs_from_args(('name',), *args, **kwargs)
+ self._fields.add(name)
+ return self.__class__.SelectTag(self, name, attrs)
+
+ def option(self, *args, **kwargs):
+ "required attribute: 'value'"
+ if self.current_select:
+ value, attrs = _attrs_from_args(('value',), *args, **kwargs)
+ return self.__class__.OptionTag(self, self.current_select, value, attrs)
+ else:
+ raise DocError("No <select> tag opened. Can't put an <option> here.")
+
+ def detached_errors(self, render_function = None):
+ self._detached_errors_pos.append((len(self.result), render_function or self.error_dict_to_string))
+ self.result.append('')
+
+ def error_dict_to_string(self, dct):
+ if dct:
+ doc, tag, text = SimpleDoc().tagtext()
+ with tag('ul', klass='error-list'):
+ for error in dct.values():
+ with tag('li'):
+ text(error)
+ return doc.getvalue()
+ else:
+ return ''
+
+
+ def getvalue(self):
+ """
+ returns the whole document as a string
+ """
+ for position, render_function in self._detached_errors_pos:
+ self.result[position] = render_function(
+ dict((name, self.errors[name]) for name in self.errors if name not in self._fields)
+ )
+ return ''.join(self.result)
+
diff --git a/yattag/indentation.py b/yattag/indentation.py
new file mode 100644
index 0000000..df0f24f
--- /dev/null
+++ b/yattag/indentation.py
@@ -0,0 +1,308 @@
+import re
+
+__all__ = ['indent']
+
+class TokenMeta(type):
+
+ _token_classes = {}
+
+ def __new__(cls, name, bases, attrs):
+ kls = type.__new__(cls, name, bases, attrs)
+ cls._token_classes[name] = kls
+ return kls
+
+ @classmethod
+ def getclass(cls, name):
+ return cls._token_classes[name]
+
+# need to proceed that way for Python 2/3 compatility:
+TokenBase = TokenMeta('TokenBase', (object,), {})
+
+class Token(TokenBase):
+ regex = None
+
+ def __init__(self, groupdict):
+ self.content = groupdict[self.__class__.__name__]
+
+class Text(Token):
+ regex = '[^<>]+'
+ def __init__(self, *args, **kwargs):
+ super(Text, self).__init__(*args, **kwargs)
+ self._isblank = None
+
+ @property
+ def isblank(self):
+ if self._isblank is None:
+ self._isblank = not self.content.strip()
+ return self._isblank
+
+class Comment(Token):
+ regex = r'<!--((?!-->).)*.?-->'
+
+class CData(Token):
+ regex = r'<!\[CDATA\[((?!\]\]>).*).?\]\]>'
+
+class Doctype(Token):
+ regex = r'''<!DOCTYPE(\s+([^<>"']+|"[^"]*"|'[^']*'))*>'''
+
+_open_tag_start = r'''
+ <\s*
+ (?P<{tag_name_key}>{tag_name_rgx})
+ (\s+[^/><"=\s]+ # attribute
+ (\s*=\s*
+ (
+ [^/><"=\s]+ | # unquoted attribute value
+ ("[^"]*") | # " quoted attribute value
+ ('[^']*') # ' quoted attribute value
+ )
+ )? # the attribute value is optional (we're forgiving)
+ )*
+ \s*'''
+
+class Script(Token):
+ _end_script = r'<\s*/\s*script\s*>'
+
+ regex = _open_tag_start.format(
+ tag_name_key = 'script_ignore',
+ tag_name_rgx = 'script',
+ ) + r'>((?!({end_script})).)*.?{end_script}'.format(
+ end_script = _end_script
+ )
+
+class Style(Token):
+ _end_style = r'<\s*/\s*style\s*>'
+
+ regex = _open_tag_start.format(
+ tag_name_key = 'style_ignore',
+ tag_name_rgx = 'style',
+ ) + r'>((?!({end_style})).)*.?{end_style}'.format(
+ end_style = _end_style
+ )
+
+class XMLDeclaration(Token):
+ regex = _open_tag_start.format(
+ tag_name_key = 'xmldecl_ignore',
+ tag_name_rgx = r'\?\s*xml'
+ ) + r'\?\s*>'
+
+class NamedTagTokenMeta(TokenMeta):
+ def __new__(cls, name, bases, attrs):
+ kls = TokenMeta.__new__(cls, name, bases, attrs)
+ if name not in('NamedTagTokenBase', 'NamedTagToken'):
+ kls.tag_name_key = 'tag_name_%s' % name
+ kls.regex = kls.regex_template.format(
+ tag_name_key = kls.tag_name_key,
+ tag_name_rgx = kls.tag_name_rgx
+ )
+ return kls
+
+# need to proceed that way for Python 2/3 compatility
+NamedTagTokenBase = NamedTagTokenMeta(
+ 'NamedTagTokenBase',
+ (Token,),
+ {'tag_name_rgx': r'[^/><"\s]+'}
+)
+
+class NamedTagToken(NamedTagTokenBase):
+ def __init__(self, groupdict):
+ super(NamedTagToken, self).__init__(groupdict)
+ self.tag_name = groupdict[self.__class__.tag_name_key]
+
+class OpenTag(NamedTagToken):
+ regex_template = _open_tag_start + '>'
+
+class SelfTag(NamedTagToken): # a self closing tag
+ regex_template = _open_tag_start + r'/\s*>'
+
+class CloseTag(NamedTagToken):
+ regex_template = r'<\s*/(?P<{tag_name_key}>{tag_name_rgx})(\s[^/><"]*)?>'
+
+class XMLTokenError(Exception):
+ pass
+
+class Tokenizer(object):
+
+ def __init__(self, token_classes):
+ self.token_classes = token_classes
+ self.token_names = [kls.__name__ for kls in token_classes]
+ self.get_token = None
+
+ def _compile_regex(self):
+ self.get_token = re.compile(
+ '|'.join(
+ '(?P<%s>%s)' % (klass.__name__, klass.regex) for klass in self.token_classes
+ ),
+ re.X | re.I | re.S
+ ).match
+
+ def tokenize(self, string):
+ if not self.get_token:
+ self._compile_regex()
+ result = []
+ append = result.append
+ while string:
+ mobj = self.get_token(string)
+ if mobj:
+ groupdict = mobj.groupdict()
+ class_name = next(name for name in self.token_names if groupdict[name])
+ token = TokenMeta.getclass(class_name)(groupdict)
+ append(token)
+ string = string[len(token.content):]
+ else:
+ raise XMLTokenError("Unrecognized XML token near %s" % repr(string[:100]))
+
+ return result
+
+tokenize = Tokenizer(
+ (Text, Comment, CData, Doctype, XMLDeclaration, Script, Style, OpenTag, SelfTag, CloseTag)
+).tokenize
+
+class TagMatcher(object):
+
+ class SameNameMatcher(object):
+ def __init__(self):
+ self.unmatched_open = []
+ self.matched = {}
+
+ def sigclose(self, i):
+ if self.unmatched_open:
+ open_tag = self.unmatched_open.pop()
+ self.matched[open_tag] = i
+ self.matched[i] = open_tag
+ return open_tag
+ else:
+ return None
+
+ def sigopen(self, i):
+ self.unmatched_open.append(i)
+
+ def __init__(self, token_list, blank_is_text = False):
+ self.token_list = token_list
+ self.name_matchers = {}
+ self.direct_text_parents = set()
+
+ for i in range(len(token_list)):
+ token = token_list[i]
+ tpe = type(token)
+ if tpe is OpenTag:
+ self._get_name_matcher(token.tag_name).sigopen(i)
+ elif tpe is CloseTag:
+ self._get_name_matcher(token.tag_name).sigclose(i)
+
+ # TODO move this somewhere else
+ current_nodes = []
+ for i in range(len(token_list)):
+ token = token_list[i]
+ tpe = type(token)
+ if tpe is OpenTag and self.ismatched(i):
+ current_nodes.append(i)
+ elif tpe is CloseTag and self.ismatched(i):
+ current_nodes.pop()
+ elif tpe is Text and (blank_is_text or not token.isblank):
+ if current_nodes:
+ self.direct_text_parents.add(current_nodes[-1])
+
+ def _get_name_matcher(self, tag_name):
+ try:
+ return self.name_matchers[tag_name]
+ except KeyError:
... 427 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/yattag.git
More information about the Python-modules-commits
mailing list