Serotonin Storm

source>template_utils>markup.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
"""
Utilities for text-to-HTML conversion.

"""


def textile(text, **kwargs):
    """
    Applies Textile conversion to a string, and returns the HTML.
    
    This is simply a pass-through to the ``textile`` template filter
    included in ``django.contrib.markup``, which works around issues
    PyTextile has with Unicode strings. If you're not using Django but
    want to use Textile with ``MarkupFormatter``, you'll need to
    supply your own Textile filter.
    
    """
    from django.contrib.markup.templatetags.markup import textile
    return textile(text)

def markdown(text, **kwargs):
    """
    Applies Markdown conversion to a string, and returns the HTML.
    
    """
    import markdown
    return markdown.markdown(text, **kwargs)

def restructuredtext(text, **kwargs):
    """
    Applies reStructuredText conversion to a string, and returns the
    HTML.
    
    """
    from docutils import core
    parts = core.publish_parts(source=text,
                               writer_name='html4css1',
                               **kwargs)
    return parts['fragment']

DEFAULT_MARKUP_FILTERS = {
    'textile': textile,
    'markdown': markdown,
    'restructuredtext': restructuredtext
    }


class MarkupFormatter(object):
    """
    Generic markup formatter which can handle multiple text-to-HTML
    conversion systems.

    
    Overview
    ========
    
    Conversion is handled by filter functions registered with an
    instance; a set of default filters is provided which cover
    Markdown, reStructuredText and Textile (though using one of these
    requires the appropriate module to be available on your system --
    e.g., using the reST filter requires you to have ``docutils``
    installed).
    
    New filters can be added by registering them with an instance;
    simply define a function which performs the conversion you want,
    and use the ``register`` method to add it; ``register`` expects
    two arguments:
    
    1. The name to associate with the filter.
    
    2. The actual filter function.
    
    So, for example, you might define a new filter function called
    ``my_filter``, and register it like so::
    
        formatter = MarkupFormatter()
        formatter.register('my_filter', my_filter)
    
    Instances are callable, so applying the conversion to a string is
    simple::
    
        my_html = formatter(my_string, filter_name='my_filter')
    
    The filter to use for conversion is determined in either of two
    ways:
    
    1. If the keyword argument ``filter_name`` is supplied, it will be
       used as the filter name.
    
    2. Absent an explicit argument, the filter name will be taken from
       the ``MARKUP_FILTER`` setting in your Django settings file (see
       below).
    
    Additionally, arbitrary keyword arguments can be supplied, and
    they will be passed on to the filter function.
    
    
    Reading default bahavior from a Django setting
    ==============================================
    
    The Django setting ``MARKUP_FILTER`` can be used to specify
    default behavior; if used, its value should be a 2-tuple:
    
    * The first element should be the name of a filter.
    
    * The second element should be a dictionary to use as keyword
      arguments for that filter.
    
    So, for example, to have the default behavior apply Markdown with
    safe mode enabled, you would add this to your Django settings
    file::
    
        MARKUP_FILTER = ('markdown', { 'safe_mode': True })
    
    The filter named in this setting does not have to be from the
    default set; as long as you register a filter of that name before
    trying to use the formatter, it will work.
    
    To have the default behavior apply no conversion whatsoever, set
    ``MARKUP_FILTER`` like so::
    
        MARKUP_FILTER = (None, {})
    
    When the ``filter_name`` keyword argument is supplied, the
    ``MARKUP_FILTER`` setting is ignored entirely -- neither a filter
    name nor any keyword arguments will be read from it. This means
    that, by always supplying ``filter_name`` explicitly, it is
    possible to use this formatter without configuring or even
    installing Django.


    Django and template autoescaping
    ================================

    Django's template system defaults to escaping the output of
    template variables, which can interfere with functions intended to
    return HTML. ``MarkupFormatter`` does not in any way tamper with
    Django's autoescaping, so pasing the results of formatting
    directly to a Django template will result in that text being
    escaped.

    If you need to use ``MarkupFormatter`` for items which will be
    passed to a Django template as variables, use the function
    ``django.utils.safestring.mark_safe`` to tell Django's template
    system not to escape that text.
    
    For convenience, a Django template filter is included (in
    ``templatetags/generic_markup.py``) which applies
    ``MarkupFormatter`` to a string and marks the result as not
    requiring autoescaping.
    
    
    Examples
    ========
    
    Using the default behavior, with the filter name and arguments
    taken from the ``MARKUP_FILTER`` setting::
    
        formatter = MarkupFormatter()
        my_string = 'Lorem ipsum dolor sit amet.\n\nConsectetuer adipiscing elit.'
        my_html = formatter(my_string)
    
    Explicitly naming the filter to use::
    
        my_html = formatter(my_string, filter_name='markdown')
    
    Passing keyword arguments::
    
        my_html = formatter(my_string, filter_name='markdown', safe_mode=True)
    
    Perform no conversion (return the text as-is)::
    
        my_html = formatter(my_string, filter_name=None)
    
    """
    def __init__(self):
        self._filters = {}
        for filter_name, filter_func in DEFAULT_MARKUP_FILTERS.items():
            self.register(filter_name, filter_func)
    
    def register(self, filter_name, filter_func):
        """
        Registers a new filter for use.
        
        """
        self._filters[filter_name] = filter_func
    
    def __call__(self, text, **kwargs):
        """
        Applies text-to-HTML conversion to a string, and returns the
        HTML.
        
        """
        if 'filter_name' in kwargs:
            filter_name = kwargs['filter_name']
            del kwargs['filter_name']
            filter_kwargs = {}
        else:
            from django.conf import settings
            filter_name, filter_kwargs = settings.MARKUP_FILTER
        if filter_name is None:
            return text
        if filter_name not in self._filters:
            raise ValueError("'%s' is not a registered markup filter. Registered filters are: %s." % (filter_name,
                                                                                                       ', '.join(self._filters.iterkeys())))
        filter_func = self._filters[filter_name]
        filter_kwargs.update(**kwargs)
        return filter_func(text, **filter_kwargs)


# Unless you need to have multiple instances of MarkupFormatter lying
# around, or want to subclass it, the easiest way to use it is to
# import this instance.

formatter = MarkupFormatter()