[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