We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Escalated number of errors from Google Search Console: Breadcrumbs
[Google Search Console](https://search.google.com/search-console/breadcrumbs/drilldown?resource_id=https%3A%2F%2Fdeveloper.mozilla.org%2F&item_key=CgwICRoIcG9zaXRpb24QAxgP&hl=en) emailed us about a big increase in indexing "errors" about breadcrumbs.
<img width="1174" alt="Screen Shot 2019-09-20 at 1 47 54 PM" src="https://user-images.githubusercontent.com/26739/65347578-4a118c80-dbad-11e9-8bda-8df0bd7871de.png">

The code that produces our breadcrumbs (on the Wiki) [hasn't changed in years](https://github.com/mozilla/kuma/blame/master/kuma/wiki/jinja2/wiki/includes/document_macros.html).

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kuma/wiki/templatetags/jinja_helpers.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 import difflib
3 import json
4 import re
5 
6 import jinja2
7 import six
8 from constance import config
9 from cssselect.parser import SelectorSyntaxError
10 from django.conf import settings
11 from django.core.serializers.json import DjangoJSONEncoder
12 from django.template import loader
13 from django.utils import lru_cache
14 from django.utils.html import conditional_escape
15 from django.utils.six.moves.urllib.parse import urlsplit, urlunparse
16 from django.utils.translation import ugettext
17 from django_jinja import library
18 
19 from kuma.core.urlresolvers import reverse
20 from kuma.core.utils import order_params, urlparams
21 from kuma.core.utils import safer_pyquery as pq
22 
23 from ..constants import DIFF_WRAP_COLUMN
24 from ..content import clean_content
25 from ..utils import tidy_content
26 
27 
28 def get_compare_url(doc, from_id, to_id):
29     return order_params(urlparams(
30         reverse('wiki.compare_revisions', args=[doc.slug], locale=doc.locale),
31         **{'from': from_id, 'to': to_id}
32     ))
33 
34 
35 @library.filter
36 def bugize_text(content):
37     content = jinja2.escape(content)
38     regex = re.compile(r'(bug)\s+#?(\d+)', re.IGNORECASE)
39     content = regex.sub(
40         jinja2.Markup('<a href="https://bugzilla.mozilla.org/'
41                       'show_bug.cgi?id=\\2" '
42                       'target="_blank" rel="noopener">\\1 \\2</a>'),
43         content)
44     return content
45 
46 
47 @library.global_function
48 def format_comment(rev, previous_revision=None, load_previous=True):
49     """
50     Format comment for HTML display, with Bugzilla links and slug changes.
51 
52     Keyword Arguments:
53     rev - The revision
54     previous_revision - The previous revision (default None)
55     load_previous - Try loading previous revision if None (default True)
56     """
57     if previous_revision is None and load_previous:
58         previous_revision = rev.previous
59     comment = bugize_text(rev.comment if rev.comment else "")
60 
61     # If a page move, say so
62     if previous_revision and previous_revision.slug != rev.slug:
63         comment += jinja2.Markup(
64             '<span class="slug-change">'
65             '<span>%s</span>'
66             ' <i class="icon-long-arrow-right" aria-hidden="true"></i> '
67             '<span>%s</span></span>') % (previous_revision.slug, rev.slug)
68 
69     return comment
70 
71 
72 @library.global_function
73 def revisions_unified_diff(from_revision, to_revision):
74     """
75     Given the two revisions generate a diff between their tidied
76     content in the unified diff format.
77     """
78     if from_revision is None or to_revision is None:
79         return "Diff is unavailable."
80 
81     fromfile = '[%s] #%s' % (from_revision.document.locale, from_revision.id)
82     tofile = '[%s] #%s' % (to_revision.document.locale, to_revision.id)
83 
84     tidy_from = from_revision.get_tidied_content()
85     tidy_to = to_revision.get_tidied_content()
86 
87     return u'\n'.join(difflib.unified_diff(
88         tidy_from.splitlines(),
89         tidy_to.splitlines(),
90         fromfile=fromfile,
91         tofile=tofile,
92     ))
93 
94 
95 @library.global_function
96 def diff_table(content_from, content_to, prev_id, curr_id, tidy=False):
97     """
98     Creates an HTML diff of the passed in content_from and content_to.
99     """
100     if tidy:
101         content_from, errors = tidy_content(content_from)
102         content_to, errors = tidy_content(content_to)
103 
104     html_diff = difflib.HtmlDiff(wrapcolumn=DIFF_WRAP_COLUMN)
105     try:
106         diff = html_diff.make_table(content_from.splitlines(),
107                                     content_to.splitlines(),
108                                     ugettext('Revision %s') % prev_id,
109                                     ugettext('Revision %s') % curr_id,
110                                     context=True,
111                                     numlines=config.DIFF_CONTEXT_LINES)
112     except RuntimeError:
113         # some diffs hit a max recursion error
114         message = ugettext(u'There was an error generating the content.')
115         diff = '<div class="warning"><p>%s</p></div>' % message
116     return jinja2.Markup(diff)
117 
118 
119 @library.global_function
120 def tag_diff_table(prev_tags, curr_tags, prev_id, curr_id):
121     html_diff = difflib.HtmlDiff(wrapcolumn=DIFF_WRAP_COLUMN)
122 
123     diff = html_diff.make_table([prev_tags], [curr_tags],
124                                 ugettext('Revision %s') % prev_id,
125                                 ugettext('Revision %s') % curr_id)
126 
127     # Simple formatting update: 784877
128     diff = diff.replace('",', '"<br />').replace('<td', '<td valign="top"')
129     return jinja2.Markup(diff)
130 
131 
132 @library.global_function
133 def colorize_diff(diff):
134     # we're doing something horrible here because this will show up
135     # in feed reader and other clients that don't load CSS files
136     diff = diff.replace('<span class="diff_add"', '<span class="diff_add" '
137                         'style="background-color: #afa; text-decoration: none;"')
138     diff = diff.replace('<span class="diff_sub"', '<span class="diff_sub" '
139                         'style="background-color: #faa; text-decoration: none;"')
140     diff = diff.replace('<span class="diff_chg"', '<span class="diff_chg" '
141                         'style="background-color: #fe0; text-decoration: none;"')
142     return diff
143 
144 
145 @library.filter
146 def wiki_bleach(val):
147     return jinja2.Markup(clean_content(val))
148 
149 
150 @library.filter
151 def selector_content_find(document, selector):
152     """
153     Provided a selector, returns the relevant content from the document
154     """
155     content = ''
156     try:
157         page = pq(document.rendered_html)
158     except ValueError:
159         # pass errors during construction
160         pass
161     try:
162         content = page.find(selector).text()
163     except SelectorSyntaxError:
164         # pass errors during find/select
165         pass
166     return content
167 
168 
169 def _recursive_escape(value, esc=conditional_escape):
170     """
171     Recursively escapes strings in an object.
172 
173     Traverses dict, list and tuples. These are the data structures supported
174     by the JSON encoder.
175     """
176     if isinstance(value, dict):
177         return type(value)((esc(k), _recursive_escape(v))
178                            for (k, v) in value.iteritems())
179     elif isinstance(value, (list, tuple)):
180         return type(value)(_recursive_escape(v) for v in value)
181     elif isinstance(value, six.string_types):
182         return esc(value)
183     elif isinstance(value, (int, long, float)) or value in (True, False, None):
184         return value
185     # We've exhausted all the types acceptable by the default JSON encoder.
186     # Django's improved JSON encoder handles a few other types, all of which
187     # are represented by strings. For these types, we apply JSON encoding
188     # immediately and then escape the result.
189     return esc(DjangoJSONEncoder().default(value))
190 
191 
192 @library.filter
193 def tojson(value):
194     """
195     Returns the JSON representation of the value.
196     """
197     try:
198         # If value contains custom subclasses of int, str, datetime, etc.
199         # arbitrary exceptions may be raised during escaping or serialization.
200         result = json.dumps(_recursive_escape(value), cls=DjangoJSONEncoder)
201     except Exception:
202         return ''
203     return jinja2.Markup(result)
204 
205 
206 @library.filter
207 def absolutify(url, for_wiki_site=False):
208     """Joins settings.SITE_URL with a URL path."""
209     if url.startswith('http'):
210         return url
211 
212     if for_wiki_site:
213         site_url = settings.WIKI_SITE_URL
214     else:
215         site_url = settings.SITE_URL
216 
217     site = urlsplit(site_url)
218     parts = urlsplit(url)
219     scheme = site.scheme
220     netloc = site.netloc
221     path = parts.path
222     query = parts.query
223     fragment = parts.fragment
224 
225     if path == '':
226         path = '/'
227 
228     return urlunparse([scheme, netloc, path, None, query, fragment])
229 
230 
231 @library.global_function
232 def wiki_url(path):
233     """
234     Create a URL pointing to Kuma.
235     Look for a wiki page in the current locale, or default to given path
236     """
237     if '#' in path:
238         slug, fragment = path.split('#', 1)
239     else:
240         slug = path
241         fragment = ''
242     new_path = reverse('wiki.document', args=[slug])
243     if fragment:
244         new_path += '#' + fragment
245     return new_path
246 
247 
248 @library.global_function
249 @lru_cache.lru_cache()
250 def include_svg(path, title=None, title_id=None):
251     """
252     Embded an SVG file by path, optionally changing the title,
253     and adding an id
254     """
255     svg = loader.get_template(path).render()
256     if (title):
257         svg_parsed = pq(svg, namespaces={'svg': 'http://www.w3.org/2000/svg'})
258         svg_parsed('svg|title')[0].text = title
259         if (title_id):
260             svg_parsed('svg|title').attr['id'] = title_id
261         svg_out = svg_parsed.outerHtml()
262     else:
263         svg_out = svg
264     return jinja2.Markup(svg_out)
265
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/kuma/wiki/templatetags/jinja_helpers.py b/kuma/wiki/templatetags/jinja_helpers.py
--- a/kuma/wiki/templatetags/jinja_helpers.py
+++ b/kuma/wiki/templatetags/jinja_helpers.py
@@ -262,3 +262,12 @@
     else:
         svg_out = svg
     return jinja2.Markup(svg_out)
+
+
+@library.filter
+def length_plus_one(lengthy):
+    """Useful when you want to do something like
+    `{{ somelist|length_plus_one }}` and you want it to output the
+    Python equivalent of `len(somelist) + 1`.
+    """
+    return len(lengthy) + 1

{"golden_diff": "diff --git a/kuma/wiki/templatetags/jinja_helpers.py b/kuma/wiki/templatetags/jinja_helpers.py\n--- a/kuma/wiki/templatetags/jinja_helpers.py\n+++ b/kuma/wiki/templatetags/jinja_helpers.py\n@@ -262,3 +262,12 @@\n     else:\n         svg_out = svg\n     return jinja2.Markup(svg_out)\n+\n+\n+@library.filter\n+def length_plus_one(lengthy):\n+    \"\"\"Useful when you want to do something like\n+    `{{ somelist|length_plus_one }}` and you want it to output the\n+    Python equivalent of `len(somelist) + 1`.\n+    \"\"\"\n+    return len(lengthy) + 1\n", "issue": "Escalated number of errors from Google Search Console: Breadcrumbs\n[Google Search Console](https://search.google.com/search-console/breadcrumbs/drilldown?resource_id=https%3A%2F%2Fdeveloper.mozilla.org%2F&item_key=CgwICRoIcG9zaXRpb24QAxgP&hl=en) emailed us about a big increase in indexing \"errors\" about breadcrumbs.\r\n<img width=\"1174\" alt=\"Screen Shot 2019-09-20 at 1 47 54 PM\" src=\"https://user-images.githubusercontent.com/26739/65347578-4a118c80-dbad-11e9-8bda-8df0bd7871de.png\">\r\n\r\nThe code that produces our breadcrumbs (on the Wiki) [hasn't changed in years](https://github.com/mozilla/kuma/blame/master/kuma/wiki/jinja2/wiki/includes/document_macros.html).\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\nimport difflib\nimport json\nimport re\n\nimport jinja2\nimport six\nfrom constance import config\nfrom cssselect.parser import SelectorSyntaxError\nfrom django.conf import settings\nfrom django.core.serializers.json import DjangoJSONEncoder\nfrom django.template import loader\nfrom django.utils import lru_cache\nfrom django.utils.html import conditional_escape\nfrom django.utils.six.moves.urllib.parse import urlsplit, urlunparse\nfrom django.utils.translation import ugettext\nfrom django_jinja import library\n\nfrom kuma.core.urlresolvers import reverse\nfrom kuma.core.utils import order_params, urlparams\nfrom kuma.core.utils import safer_pyquery as pq\n\nfrom ..constants import DIFF_WRAP_COLUMN\nfrom ..content import clean_content\nfrom ..utils import tidy_content\n\n\ndef get_compare_url(doc, from_id, to_id):\n    return order_params(urlparams(\n        reverse('wiki.compare_revisions', args=[doc.slug], locale=doc.locale),\n        **{'from': from_id, 'to': to_id}\n    ))\n\n\n@library.filter\ndef bugize_text(content):\n    content = jinja2.escape(content)\n    regex = re.compile(r'(bug)\\s+#?(\\d+)', re.IGNORECASE)\n    content = regex.sub(\n        jinja2.Markup('<a href=\"https://bugzilla.mozilla.org/'\n                      'show_bug.cgi?id=\\\\2\" '\n                      'target=\"_blank\" rel=\"noopener\">\\\\1 \\\\2</a>'),\n        content)\n    return content\n\n\n@library.global_function\ndef format_comment(rev, previous_revision=None, load_previous=True):\n    \"\"\"\n    Format comment for HTML display, with Bugzilla links and slug changes.\n\n    Keyword Arguments:\n    rev - The revision\n    previous_revision - The previous revision (default None)\n    load_previous - Try loading previous revision if None (default True)\n    \"\"\"\n    if previous_revision is None and load_previous:\n        previous_revision = rev.previous\n    comment = bugize_text(rev.comment if rev.comment else \"\")\n\n    # If a page move, say so\n    if previous_revision and previous_revision.slug != rev.slug:\n        comment += jinja2.Markup(\n            '<span class=\"slug-change\">'\n            '<span>%s</span>'\n            ' <i class=\"icon-long-arrow-right\" aria-hidden=\"true\"></i> '\n            '<span>%s</span></span>') % (previous_revision.slug, rev.slug)\n\n    return comment\n\n\n@library.global_function\ndef revisions_unified_diff(from_revision, to_revision):\n    \"\"\"\n    Given the two revisions generate a diff between their tidied\n    content in the unified diff format.\n    \"\"\"\n    if from_revision is None or to_revision is None:\n        return \"Diff is unavailable.\"\n\n    fromfile = '[%s] #%s' % (from_revision.document.locale, from_revision.id)\n    tofile = '[%s] #%s' % (to_revision.document.locale, to_revision.id)\n\n    tidy_from = from_revision.get_tidied_content()\n    tidy_to = to_revision.get_tidied_content()\n\n    return u'\\n'.join(difflib.unified_diff(\n        tidy_from.splitlines(),\n        tidy_to.splitlines(),\n        fromfile=fromfile,\n        tofile=tofile,\n    ))\n\n\n@library.global_function\ndef diff_table(content_from, content_to, prev_id, curr_id, tidy=False):\n    \"\"\"\n    Creates an HTML diff of the passed in content_from and content_to.\n    \"\"\"\n    if tidy:\n        content_from, errors = tidy_content(content_from)\n        content_to, errors = tidy_content(content_to)\n\n    html_diff = difflib.HtmlDiff(wrapcolumn=DIFF_WRAP_COLUMN)\n    try:\n        diff = html_diff.make_table(content_from.splitlines(),\n                                    content_to.splitlines(),\n                                    ugettext('Revision %s') % prev_id,\n                                    ugettext('Revision %s') % curr_id,\n                                    context=True,\n                                    numlines=config.DIFF_CONTEXT_LINES)\n    except RuntimeError:\n        # some diffs hit a max recursion error\n        message = ugettext(u'There was an error generating the content.')\n        diff = '<div class=\"warning\"><p>%s</p></div>' % message\n    return jinja2.Markup(diff)\n\n\n@library.global_function\ndef tag_diff_table(prev_tags, curr_tags, prev_id, curr_id):\n    html_diff = difflib.HtmlDiff(wrapcolumn=DIFF_WRAP_COLUMN)\n\n    diff = html_diff.make_table([prev_tags], [curr_tags],\n                                ugettext('Revision %s') % prev_id,\n                                ugettext('Revision %s') % curr_id)\n\n    # Simple formatting update: 784877\n    diff = diff.replace('\",', '\"<br />').replace('<td', '<td valign=\"top\"')\n    return jinja2.Markup(diff)\n\n\n@library.global_function\ndef colorize_diff(diff):\n    # we're doing something horrible here because this will show up\n    # in feed reader and other clients that don't load CSS files\n    diff = diff.replace('<span class=\"diff_add\"', '<span class=\"diff_add\" '\n                        'style=\"background-color: #afa; text-decoration: none;\"')\n    diff = diff.replace('<span class=\"diff_sub\"', '<span class=\"diff_sub\" '\n                        'style=\"background-color: #faa; text-decoration: none;\"')\n    diff = diff.replace('<span class=\"diff_chg\"', '<span class=\"diff_chg\" '\n                        'style=\"background-color: #fe0; text-decoration: none;\"')\n    return diff\n\n\n@library.filter\ndef wiki_bleach(val):\n    return jinja2.Markup(clean_content(val))\n\n\n@library.filter\ndef selector_content_find(document, selector):\n    \"\"\"\n    Provided a selector, returns the relevant content from the document\n    \"\"\"\n    content = ''\n    try:\n        page = pq(document.rendered_html)\n    except ValueError:\n        # pass errors during construction\n        pass\n    try:\n        content = page.find(selector).text()\n    except SelectorSyntaxError:\n        # pass errors during find/select\n        pass\n    return content\n\n\ndef _recursive_escape(value, esc=conditional_escape):\n    \"\"\"\n    Recursively escapes strings in an object.\n\n    Traverses dict, list and tuples. These are the data structures supported\n    by the JSON encoder.\n    \"\"\"\n    if isinstance(value, dict):\n        return type(value)((esc(k), _recursive_escape(v))\n                           for (k, v) in value.iteritems())\n    elif isinstance(value, (list, tuple)):\n        return type(value)(_recursive_escape(v) for v in value)\n    elif isinstance(value, six.string_types):\n        return esc(value)\n    elif isinstance(value, (int, long, float)) or value in (True, False, None):\n        return value\n    # We've exhausted all the types acceptable by the default JSON encoder.\n    # Django's improved JSON encoder handles a few other types, all of which\n    # are represented by strings. For these types, we apply JSON encoding\n    # immediately and then escape the result.\n    return esc(DjangoJSONEncoder().default(value))\n\n\n@library.filter\ndef tojson(value):\n    \"\"\"\n    Returns the JSON representation of the value.\n    \"\"\"\n    try:\n        # If value contains custom subclasses of int, str, datetime, etc.\n        # arbitrary exceptions may be raised during escaping or serialization.\n        result = json.dumps(_recursive_escape(value), cls=DjangoJSONEncoder)\n    except Exception:\n        return ''\n    return jinja2.Markup(result)\n\n\n@library.filter\ndef absolutify(url, for_wiki_site=False):\n    \"\"\"Joins settings.SITE_URL with a URL path.\"\"\"\n    if url.startswith('http'):\n        return url\n\n    if for_wiki_site:\n        site_url = settings.WIKI_SITE_URL\n    else:\n        site_url = settings.SITE_URL\n\n    site = urlsplit(site_url)\n    parts = urlsplit(url)\n    scheme = site.scheme\n    netloc = site.netloc\n    path = parts.path\n    query = parts.query\n    fragment = parts.fragment\n\n    if path == '':\n        path = '/'\n\n    return urlunparse([scheme, netloc, path, None, query, fragment])\n\n\n@library.global_function\ndef wiki_url(path):\n    \"\"\"\n    Create a URL pointing to Kuma.\n    Look for a wiki page in the current locale, or default to given path\n    \"\"\"\n    if '#' in path:\n        slug, fragment = path.split('#', 1)\n    else:\n        slug = path\n        fragment = ''\n    new_path = reverse('wiki.document', args=[slug])\n    if fragment:\n        new_path += '#' + fragment\n    return new_path\n\n\n@library.global_function\n@lru_cache.lru_cache()\ndef include_svg(path, title=None, title_id=None):\n    \"\"\"\n    Embded an SVG file by path, optionally changing the title,\n    and adding an id\n    \"\"\"\n    svg = loader.get_template(path).render()\n    if (title):\n        svg_parsed = pq(svg, namespaces={'svg': 'http://www.w3.org/2000/svg'})\n        svg_parsed('svg|title')[0].text = title\n        if (title_id):\n            svg_parsed('svg|title').attr['id'] = title_id\n        svg_out = svg_parsed.outerHtml()\n    else:\n        svg_out = svg\n    return jinja2.Markup(svg_out)\n", "path": "kuma/wiki/templatetags/jinja_helpers.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\nimport difflib\nimport json\nimport re\n\nimport jinja2\nimport six\nfrom constance import config\nfrom cssselect.parser import SelectorSyntaxError\nfrom django.conf import settings\nfrom django.core.serializers.json import DjangoJSONEncoder\nfrom django.template import loader\nfrom django.utils import lru_cache\nfrom django.utils.html import conditional_escape\nfrom django.utils.six.moves.urllib.parse import urlsplit, urlunparse\nfrom django.utils.translation import ugettext\nfrom django_jinja import library\n\nfrom kuma.core.urlresolvers import reverse\nfrom kuma.core.utils import order_params, urlparams\nfrom kuma.core.utils import safer_pyquery as pq\n\nfrom ..constants import DIFF_WRAP_COLUMN\nfrom ..content import clean_content\nfrom ..utils import tidy_content\n\n\ndef get_compare_url(doc, from_id, to_id):\n    return order_params(urlparams(\n        reverse('wiki.compare_revisions', args=[doc.slug], locale=doc.locale),\n        **{'from': from_id, 'to': to_id}\n    ))\n\n\n@library.filter\ndef bugize_text(content):\n    content = jinja2.escape(content)\n    regex = re.compile(r'(bug)\\s+#?(\\d+)', re.IGNORECASE)\n    content = regex.sub(\n        jinja2.Markup('<a href=\"https://bugzilla.mozilla.org/'\n                      'show_bug.cgi?id=\\\\2\" '\n                      'target=\"_blank\" rel=\"noopener\">\\\\1 \\\\2</a>'),\n        content)\n    return content\n\n\n@library.global_function\ndef format_comment(rev, previous_revision=None, load_previous=True):\n    \"\"\"\n    Format comment for HTML display, with Bugzilla links and slug changes.\n\n    Keyword Arguments:\n    rev - The revision\n    previous_revision - The previous revision (default None)\n    load_previous - Try loading previous revision if None (default True)\n    \"\"\"\n    if previous_revision is None and load_previous:\n        previous_revision = rev.previous\n    comment = bugize_text(rev.comment if rev.comment else \"\")\n\n    # If a page move, say so\n    if previous_revision and previous_revision.slug != rev.slug:\n        comment += jinja2.Markup(\n            '<span class=\"slug-change\">'\n            '<span>%s</span>'\n            ' <i class=\"icon-long-arrow-right\" aria-hidden=\"true\"></i> '\n            '<span>%s</span></span>') % (previous_revision.slug, rev.slug)\n\n    return comment\n\n\n@library.global_function\ndef revisions_unified_diff(from_revision, to_revision):\n    \"\"\"\n    Given the two revisions generate a diff between their tidied\n    content in the unified diff format.\n    \"\"\"\n    if from_revision is None or to_revision is None:\n        return \"Diff is unavailable.\"\n\n    fromfile = '[%s] #%s' % (from_revision.document.locale, from_revision.id)\n    tofile = '[%s] #%s' % (to_revision.document.locale, to_revision.id)\n\n    tidy_from = from_revision.get_tidied_content()\n    tidy_to = to_revision.get_tidied_content()\n\n    return u'\\n'.join(difflib.unified_diff(\n        tidy_from.splitlines(),\n        tidy_to.splitlines(),\n        fromfile=fromfile,\n        tofile=tofile,\n    ))\n\n\n@library.global_function\ndef diff_table(content_from, content_to, prev_id, curr_id, tidy=False):\n    \"\"\"\n    Creates an HTML diff of the passed in content_from and content_to.\n    \"\"\"\n    if tidy:\n        content_from, errors = tidy_content(content_from)\n        content_to, errors = tidy_content(content_to)\n\n    html_diff = difflib.HtmlDiff(wrapcolumn=DIFF_WRAP_COLUMN)\n    try:\n        diff = html_diff.make_table(content_from.splitlines(),\n                                    content_to.splitlines(),\n                                    ugettext('Revision %s') % prev_id,\n                                    ugettext('Revision %s') % curr_id,\n                                    context=True,\n                                    numlines=config.DIFF_CONTEXT_LINES)\n    except RuntimeError:\n        # some diffs hit a max recursion error\n        message = ugettext(u'There was an error generating the content.')\n        diff = '<div class=\"warning\"><p>%s</p></div>' % message\n    return jinja2.Markup(diff)\n\n\n@library.global_function\ndef tag_diff_table(prev_tags, curr_tags, prev_id, curr_id):\n    html_diff = difflib.HtmlDiff(wrapcolumn=DIFF_WRAP_COLUMN)\n\n    diff = html_diff.make_table([prev_tags], [curr_tags],\n                                ugettext('Revision %s') % prev_id,\n                                ugettext('Revision %s') % curr_id)\n\n    # Simple formatting update: 784877\n    diff = diff.replace('\",', '\"<br />').replace('<td', '<td valign=\"top\"')\n    return jinja2.Markup(diff)\n\n\n@library.global_function\ndef colorize_diff(diff):\n    # we're doing something horrible here because this will show up\n    # in feed reader and other clients that don't load CSS files\n    diff = diff.replace('<span class=\"diff_add\"', '<span class=\"diff_add\" '\n                        'style=\"background-color: #afa; text-decoration: none;\"')\n    diff = diff.replace('<span class=\"diff_sub\"', '<span class=\"diff_sub\" '\n                        'style=\"background-color: #faa; text-decoration: none;\"')\n    diff = diff.replace('<span class=\"diff_chg\"', '<span class=\"diff_chg\" '\n                        'style=\"background-color: #fe0; text-decoration: none;\"')\n    return diff\n\n\n@library.filter\ndef wiki_bleach(val):\n    return jinja2.Markup(clean_content(val))\n\n\n@library.filter\ndef selector_content_find(document, selector):\n    \"\"\"\n    Provided a selector, returns the relevant content from the document\n    \"\"\"\n    content = ''\n    try:\n        page = pq(document.rendered_html)\n    except ValueError:\n        # pass errors during construction\n        pass\n    try:\n        content = page.find(selector).text()\n    except SelectorSyntaxError:\n        # pass errors during find/select\n        pass\n    return content\n\n\ndef _recursive_escape(value, esc=conditional_escape):\n    \"\"\"\n    Recursively escapes strings in an object.\n\n    Traverses dict, list and tuples. These are the data structures supported\n    by the JSON encoder.\n    \"\"\"\n    if isinstance(value, dict):\n        return type(value)((esc(k), _recursive_escape(v))\n                           for (k, v) in value.iteritems())\n    elif isinstance(value, (list, tuple)):\n        return type(value)(_recursive_escape(v) for v in value)\n    elif isinstance(value, six.string_types):\n        return esc(value)\n    elif isinstance(value, (int, long, float)) or value in (True, False, None):\n        return value\n    # We've exhausted all the types acceptable by the default JSON encoder.\n    # Django's improved JSON encoder handles a few other types, all of which\n    # are represented by strings. For these types, we apply JSON encoding\n    # immediately and then escape the result.\n    return esc(DjangoJSONEncoder().default(value))\n\n\n@library.filter\ndef tojson(value):\n    \"\"\"\n    Returns the JSON representation of the value.\n    \"\"\"\n    try:\n        # If value contains custom subclasses of int, str, datetime, etc.\n        # arbitrary exceptions may be raised during escaping or serialization.\n        result = json.dumps(_recursive_escape(value), cls=DjangoJSONEncoder)\n    except Exception:\n        return ''\n    return jinja2.Markup(result)\n\n\n@library.filter\ndef absolutify(url, for_wiki_site=False):\n    \"\"\"Joins settings.SITE_URL with a URL path.\"\"\"\n    if url.startswith('http'):\n        return url\n\n    if for_wiki_site:\n        site_url = settings.WIKI_SITE_URL\n    else:\n        site_url = settings.SITE_URL\n\n    site = urlsplit(site_url)\n    parts = urlsplit(url)\n    scheme = site.scheme\n    netloc = site.netloc\n    path = parts.path\n    query = parts.query\n    fragment = parts.fragment\n\n    if path == '':\n        path = '/'\n\n    return urlunparse([scheme, netloc, path, None, query, fragment])\n\n\n@library.global_function\ndef wiki_url(path):\n    \"\"\"\n    Create a URL pointing to Kuma.\n    Look for a wiki page in the current locale, or default to given path\n    \"\"\"\n    if '#' in path:\n        slug, fragment = path.split('#', 1)\n    else:\n        slug = path\n        fragment = ''\n    new_path = reverse('wiki.document', args=[slug])\n    if fragment:\n        new_path += '#' + fragment\n    return new_path\n\n\n@library.global_function\n@lru_cache.lru_cache()\ndef include_svg(path, title=None, title_id=None):\n    \"\"\"\n    Embded an SVG file by path, optionally changing the title,\n    and adding an id\n    \"\"\"\n    svg = loader.get_template(path).render()\n    if (title):\n        svg_parsed = pq(svg, namespaces={'svg': 'http://www.w3.org/2000/svg'})\n        svg_parsed('svg|title')[0].text = title\n        if (title_id):\n            svg_parsed('svg|title').attr['id'] = title_id\n        svg_out = svg_parsed.outerHtml()\n    else:\n        svg_out = svg\n    return jinja2.Markup(svg_out)\n\n\n@library.filter\ndef length_plus_one(lengthy):\n    \"\"\"Useful when you want to do something like\n    `{{ somelist|length_plus_one }}` and you want it to output the\n    Python equivalent of `len(somelist) + 1`.\n    \"\"\"\n    return len(lengthy) + 1\n", "path": "kuma/wiki/templatetags/jinja_helpers.py"}]}