aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/web/base.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/web/base.py')
-rw-r--r--lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/web/base.py806
1 files changed, 0 insertions, 806 deletions
diff --git a/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/web/base.py b/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/web/base.py
deleted file mode 100644
index 47a3f344..00000000
--- a/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/web/base.py
+++ /dev/null
@@ -1,806 +0,0 @@
-# This file is part of Buildbot. Buildbot is free software: you can
-# redistribute it and/or modify it under the terms of the GNU General Public
-# License as published by the Free Software Foundation, version 2.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-# details.
-#
-# You should have received a copy of the GNU General Public License along with
-# this program; if not, write to the Free Software Foundation, Inc., 51
-# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# Copyright Buildbot Team Members
-
-
-import urlparse, urllib, time, re
-import os, cgi, sys, locale
-import jinja2
-from zope.interface import Interface
-from twisted.internet import defer
-from twisted.web import resource, static, server
-from twisted.python import log
-from buildbot.status import builder, buildstep, build
-from buildbot.status.results import SUCCESS, WARNINGS, FAILURE, SKIPPED
-from buildbot.status.results import EXCEPTION, RETRY
-from buildbot import version, util
-from buildbot.process.properties import Properties
-
-class ITopBox(Interface):
- """I represent a box in the top row of the waterfall display: the one
- which shows the status of the last build for each builder."""
- def getBox(self, request):
- """Return a Box instance, which can produce a <td> cell.
- """
-
-class ICurrentBox(Interface):
- """I represent the 'current activity' box, just above the builder name."""
- def getBox(self, status):
- """Return a Box instance, which can produce a <td> cell.
- """
-
-class IBox(Interface):
- """I represent a box in the waterfall display."""
- def getBox(self, request):
- """Return a Box instance, which wraps an Event and can produce a <td>
- cell.
- """
-
-class IHTMLLog(Interface):
- pass
-
-css_classes = {SUCCESS: "success",
- WARNINGS: "warnings",
- FAILURE: "failure",
- SKIPPED: "skipped",
- EXCEPTION: "exception",
- RETRY: "retry",
- None: "",
- }
-
-
-def getAndCheckProperties(req):
- """
- Fetch custom build properties from the HTTP request of a "Force build" or
- "Resubmit build" HTML form.
- Check the names for valid strings, and return None if a problem is found.
- Return a new Properties object containing each property found in req.
- """
- master = req.site.buildbot_service.master
- pname_validate = master.config.validation['property_name']
- pval_validate = master.config.validation['property_value']
- properties = Properties()
- i = 1
- while True:
- pname = req.args.get("property%dname" % i, [""])[0]
- pvalue = req.args.get("property%dvalue" % i, [""])[0]
- if not pname:
- break
- if not pname_validate.match(pname) \
- or not pval_validate.match(pvalue):
- log.msg("bad property name='%s', value='%s'" % (pname, pvalue))
- return None
- properties.setProperty(pname, pvalue, "Force Build Form")
- i = i + 1
-
- return properties
-
-def build_get_class(b):
- """
- Return the class to use for a finished build or buildstep,
- based on the result.
- """
- # FIXME: this getResults duplicity might need to be fixed
- result = b.getResults()
- if isinstance(b, build.BuildStatus):
- result = b.getResults()
- elif isinstance(b, buildstep.BuildStepStatus):
- result = b.getResults()[0]
- # after forcing a build, b.getResults() returns ((None, []), []), ugh
- if isinstance(result, tuple):
- result = result[0]
- else:
- raise TypeError, "%r is not a BuildStatus or BuildStepStatus" % b
-
- if result == None:
- # FIXME: this happens when a buildstep is running ?
- return "running"
- return builder.Results[result]
-
-def path_to_root(request):
- # /waterfall : ['waterfall'] -> './'
- # /somewhere/lower : ['somewhere', 'lower'] -> '../'
- # /somewhere/indexy/ : ['somewhere', 'indexy', ''] -> '../../'
- # / : [] -> './'
- if request.prepath:
- segs = len(request.prepath) - 1
- else:
- segs = 0
- root = "../" * segs if segs else './'
- return root
-
-def path_to_authfail(request):
- return path_to_root(request) + "authfail"
-
-def path_to_authzfail(request):
- return path_to_root(request) + "authzfail"
-
-def path_to_builder(request, builderstatus):
- return (path_to_root(request) +
- "builders/" +
- urllib.quote(builderstatus.getName(), safe=''))
-
-def path_to_build(request, buildstatus):
- return (path_to_builder(request, buildstatus.getBuilder()) +
- "/builds/%d" % buildstatus.getNumber())
-
-def path_to_step(request, stepstatus):
- return (path_to_build(request, stepstatus.getBuild()) +
- "/steps/%s" % urllib.quote(stepstatus.getName(), safe=''))
-
-def path_to_slave(request, slave):
- return (path_to_root(request) +
- "buildslaves/" +
- urllib.quote(slave.getName(), safe=''))
-
-def path_to_change(request, change):
- return (path_to_root(request) +
- "changes/%s" % change.number)
-
-class Box:
- # a Box wraps an Event. The Box has HTML <td> parameters that Events
- # lack, and it has a base URL to which each File's name is relative.
- # Events don't know about HTML.
- spacer = False
- def __init__(self, text=[], class_=None, urlbase=None,
- **parms):
- self.text = text
- self.class_ = class_
- self.urlbase = urlbase
- self.show_idle = 0
- if parms.has_key('show_idle'):
- del parms['show_idle']
- self.show_idle = 1
-
- self.parms = parms
- # parms is a dict of HTML parameters for the <td> element that will
- # represent this Event in the waterfall display.
-
- def td(self, **props):
- props.update(self.parms)
- text = self.text
- if not text and self.show_idle:
- text = ["[idle]"]
- props['class'] = self.class_
- props['text'] = text;
- return props
-
-
-class AccessorMixin(object):
- def getStatus(self, request):
- return request.site.buildbot_service.getStatus()
-
- def getPageTitle(self, request):
- return self.pageTitle
-
- def getAuthz(self, request):
- return request.site.buildbot_service.authz
-
- def getBuildmaster(self, request):
- return request.site.buildbot_service.master
-
-
-class ContextMixin(AccessorMixin):
- def getContext(self, request):
- status = self.getStatus(request)
- rootpath = path_to_root(request)
- locale_enc = locale.getdefaultlocale()[1]
- if locale_enc is not None:
- locale_tz = unicode(time.tzname[time.localtime()[-1]], locale_enc)
- else:
- locale_tz = unicode(time.tzname[time.localtime()[-1]])
- return dict(title_url = status.getTitleURL(),
- title = status.getTitle(),
- stylesheet = rootpath + 'default.css',
- path_to_root = rootpath,
- version = version,
- time = time.strftime("%a %d %b %Y %H:%M:%S",
- time.localtime(util.now())),
- tz = locale_tz,
- metatags = [],
- pageTitle = self.getPageTitle(request),
- welcomeurl = rootpath,
- authz = self.getAuthz(request),
- request = request,
- alert_msg = request.args.get("alert_msg", [""])[0],
- )
-
-
-class ActionResource(resource.Resource, AccessorMixin):
- """A resource that performs some action, then redirects to a new URL."""
-
- isLeaf = 1
-
- def getChild(self, name, request):
- return self
-
- def performAction(self, request):
- """
- Perform the action, and return the URL to redirect to
-
- @param request: the web request
- @returns: URL via Deferred
- can also return (URL, alert_msg) to display simple
- feedback to user in case of failure
- """
-
- def render(self, request):
- d = defer.maybeDeferred(lambda : self.performAction(request))
- def redirect(url):
- if isinstance(url, tuple):
- url, alert_msg = url
- if alert_msg:
- url += "?alert_msg="+urllib.quote(alert_msg, safe='')
- request.redirect(url)
- request.write("see <a href='%s'>%s</a>" % (url,url))
- try:
- request.finish()
- except RuntimeError:
- # this occurs when the client has already disconnected; ignore
- # it (see #2027)
- log.msg("http client disconnected before results were sent")
- d.addCallback(redirect)
-
- def fail(f):
- request.processingFailed(f)
- return None # processingFailed will log this for us
- d.addErrback(fail)
- return server.NOT_DONE_YET
-
-class HtmlResource(resource.Resource, ContextMixin):
- # this is a cheap sort of template thingy
- contentType = "text/html; charset=utf-8"
- pageTitle = "Buildbot"
- addSlash = False # adapted from Nevow
-
- def getChild(self, path, request):
- if self.addSlash and path == "" and len(request.postpath) == 0:
- return self
- return resource.Resource.getChild(self, path, request)
-
-
- def content(self, req, context):
- """
- Generate content using the standard layout and the result of the C{body}
- method.
-
- This is suitable for the case where a resource just wants to generate
- the body of a page. It depends on another method, C{body}, being
- defined to accept the request object and return a C{str}. C{render}
- will call this method and to generate the response body.
- """
- body = self.body(req)
- context['content'] = body
- template = req.site.buildbot_service.templates.get_template(
- "empty.html")
- return template.render(**context)
-
-
- def render(self, request):
- # tell the WebStatus about the HTTPChannel that got opened, so they
- # can close it if we get reconfigured and the WebStatus goes away.
- # They keep a weakref to this, since chances are good that it will be
- # closed by the browser or by us before we get reconfigured. See
- # ticket #102 for details.
- if hasattr(request, "channel"):
- # web.distrib.Request has no .channel
- request.site.buildbot_service.registerChannel(request.channel)
-
- # Our pages no longer require that their URL end in a slash. Instead,
- # they all use request.childLink() or some equivalent which takes the
- # last path component into account. This clause is left here for
- # historical and educational purposes.
- if False and self.addSlash and request.prepath[-1] != '':
- # this is intended to behave like request.URLPath().child('')
- # but we need a relative URL, since we might be living behind a
- # reverse proxy
- #
- # note that the Location: header (as used in redirects) are
- # required to have absolute URIs, and my attempt to handle
- # reverse-proxies gracefully violates rfc2616. This frequently
- # works, but single-component paths sometimes break. The best
- # strategy is to avoid these redirects whenever possible by using
- # HREFs with trailing slashes, and only use the redirects for
- # manually entered URLs.
- url = request.prePathURL()
- scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
- new_url = request.prepath[-1] + "/"
- if query:
- new_url += "?" + query
- request.redirect(new_url)
- return ''
-
- ctx = self.getContext(request)
-
- d = defer.maybeDeferred(lambda : self.content(request, ctx))
- def handle(data):
- request.setHeader("X-Clacks-Overhead", 'GNU N%C3%B3ir%C3%ADn Plunkett')
- if isinstance(data, unicode):
- data = data.encode("utf-8")
- request.setHeader("content-type", self.contentType)
- if request.method == "HEAD":
- request.setHeader("content-length", len(data))
- return ''
- return data
- d.addCallback(handle)
- def ok(data):
- request.write(data)
- try:
- request.finish()
- except RuntimeError:
- # this occurs when the client has already disconnected; ignore
- # it (see #2027)
- log.msg("http client disconnected before results were sent")
- def fail(f):
- request.processingFailed(f)
- return None # processingFailed will log this for us
- d.addCallbacks(ok, fail)
- return server.NOT_DONE_YET
-
-class StaticHTML(HtmlResource):
- def __init__(self, body, pageTitle):
- HtmlResource.__init__(self)
- self.bodyHTML = body
- self.pageTitle = pageTitle
- def content(self, request, cxt):
- cxt['content'] = self.bodyHTML
- cxt['pageTitle'] = self.pageTitle
- template = request.site.buildbot_service.templates.get_template("empty.html")
- return template.render(**cxt)
-
-class DirectoryLister(static.DirectoryLister, ContextMixin):
- """This variant of the static.DirectoryLister uses a template
- for rendering."""
-
- pageTitle = 'BuildBot'
-
- def render(self, request):
- cxt = self.getContext(request)
-
- if self.dirs is None:
- directory = os.listdir(self.path)
- directory.sort()
- else:
- directory = self.dirs
-
- dirs, files = self._getFilesAndDirectories(directory)
-
- cxt['path'] = cgi.escape(urllib.unquote(request.uri))
- cxt['directories'] = dirs
- cxt['files'] = files
- template = request.site.buildbot_service.templates.get_template("directory.html")
- data = template.render(**cxt)
- if isinstance(data, unicode):
- data = data.encode("utf-8")
- return data
-
-class StaticFile(static.File):
- """This class adds support for templated directory
- views."""
-
- def directoryListing(self):
- return DirectoryLister(self.path,
- self.listNames(),
- self.contentTypes,
- self.contentEncodings,
- self.defaultType)
-
-
-MINUTE = 60
-HOUR = 60*MINUTE
-DAY = 24*HOUR
-WEEK = 7*DAY
-MONTH = 30*DAY
-
-def plural(word, words, num):
- if int(num) == 1:
- return "%d %s" % (num, word)
- else:
- return "%d %s" % (num, words)
-
-def abbreviate_age(age):
- if age <= 90:
- return "%s ago" % plural("second", "seconds", age)
- if age < 90*MINUTE:
- return "about %s ago" % plural("minute", "minutes", age / MINUTE)
- if age < DAY:
- return "about %s ago" % plural("hour", "hours", age / HOUR)
- if age < 2*WEEK:
- return "about %s ago" % plural("day", "days", age / DAY)
- if age < 2*MONTH:
- return "about %s ago" % plural("week", "weeks", age / WEEK)
- return "a long time ago"
-
-
-class BuildLineMixin:
- LINE_TIME_FORMAT = "%b %d %H:%M"
-
- def get_line_values(self, req, build, include_builder=True):
- '''
- Collect the data needed for each line display
- '''
- builder_name = build.getBuilder().getName()
- results = build.getResults()
- text = build.getText()
- all_got_revision = build.getAllGotRevisions()
- css_class = css_classes.get(results, "")
- ss_list = build.getSourceStamps()
- if ss_list:
- repo = ss_list[0].repository
- if all_got_revision:
- if len(ss_list) == 1:
- rev = all_got_revision.get(ss_list[0].codebase, "??")
- else:
- rev = "multiple rev."
- else:
- rev = "??"
- else:
- repo = 'unknown, no information in build'
- rev = 'unknown'
-
- if type(text) == list:
- text = " ".join(text)
-
- values = {'class': css_class,
- 'builder_name': builder_name,
- 'buildnum': build.getNumber(),
- 'results': css_class,
- 'text': " ".join(build.getText()),
- 'buildurl': path_to_build(req, build),
- 'builderurl': path_to_builder(req, build.getBuilder()),
- 'rev': rev,
- 'rev_repo' : repo,
- 'time': time.strftime(self.LINE_TIME_FORMAT,
- time.localtime(build.getTimes()[0])),
- 'text': text,
- 'include_builder': include_builder
- }
- return values
-
-def map_branches(branches):
- # when the query args say "trunk", present that to things like
- # IBuilderStatus.generateFinishedBuilds as None, since that's the
- # convention in use. But also include 'trunk', because some VC systems
- # refer to it that way. In the long run we should clean this up better,
- # maybe with Branch objects or something.
- if "trunk" in branches:
- return branches + [None]
- return branches
-
-
-# jinja utilities
-
-def createJinjaEnv(revlink=None, changecommentlink=None,
- repositories=None, projects=None, jinja_loaders=None):
- ''' Create a jinja environment changecommentlink is used to
- render HTML in the WebStatus and for mail changes
-
- @type changecommentlink: C{None}, tuple (2 or 3 strings), dict (string -> 2- or 3-tuple) or callable
- @param changecommentlink: see changelinkfilter()
-
- @type revlink: C{None}, format-string, dict (repository -> format string) or callable
- @param revlink: see revlinkfilter()
-
- @type repositories: C{None} or dict (string -> url)
- @param repositories: an (optinal) mapping from repository identifiers
- (as given by Change sources) to URLs. Is used to create a link
- on every place where a repository is listed in the WebStatus.
-
- @type projects: C{None} or dict (string -> url)
- @param projects: similar to repositories, but for projects.
- '''
-
- # See http://buildbot.net/trac/ticket/658
- assert not hasattr(sys, "frozen"), 'Frozen config not supported with jinja (yet)'
-
- all_loaders = [jinja2.FileSystemLoader(os.path.join(os.getcwd(), 'templates'))]
- if jinja_loaders:
- all_loaders.extend(jinja_loaders)
- all_loaders.append(jinja2.PackageLoader('buildbot.status.web', 'templates'))
- loader = jinja2.ChoiceLoader(all_loaders)
-
- env = jinja2.Environment(loader=loader,
- extensions=['jinja2.ext.i18n'],
- trim_blocks=True,
- undefined=AlmostStrictUndefined)
-
- env.install_null_translations() # needed until we have a proper i18n backend
-
- env.tests['mapping'] = lambda obj : isinstance(obj, dict)
-
- env.filters.update(dict(
- urlencode = urllib.quote,
- email = emailfilter,
- user = userfilter,
- shortrev = shortrevfilter(revlink, env),
- revlink = revlinkfilter(revlink, env),
- changecomment = changelinkfilter(changecommentlink),
- repolink = dictlinkfilter(repositories),
- projectlink = dictlinkfilter(projects)
- ))
-
- return env
-
-def emailfilter(value):
- ''' Escape & obfuscate e-mail addresses
-
- replacing @ with <span style="display:none> reportedly works well against web-spiders
- and the next level is to use rot-13 (or something) and decode in javascript '''
-
- user = jinja2.escape(value)
- obfuscator = jinja2.Markup('<span style="display:none">ohnoyoudont</span>@')
- output = user.replace('@', obfuscator)
- return output
-
-
-def userfilter(value):
- ''' Hide e-mail address from user name when viewing changes
-
- We still include the (obfuscated) e-mail so that we can show
- it on mouse-over or similar etc
- '''
- r = re.compile('(.*) +<(.*)>')
- m = r.search(value)
- if m:
- user = jinja2.escape(m.group(1))
- email = emailfilter(m.group(2))
- return jinja2.Markup('<div class="user">%s<div class="email">%s</div></div>' % (user, email))
- else:
- return emailfilter(value) # filter for emails here for safety
-
-def _revlinkcfg(replace, templates):
- '''Helper function that returns suitable macros and functions
- for building revision links depending on replacement mechanism
-'''
-
- assert not replace or callable(replace) or isinstance(replace, dict) or \
- isinstance(replace, str) or isinstance(replace, unicode)
-
- if not replace:
- return lambda rev, repo: None
- else:
- if callable(replace):
- return lambda rev, repo: replace(rev, repo)
- elif isinstance(replace, dict): # TODO: test for [] instead
- def filter(rev, repo):
- url = replace.get(repo)
- if url:
- return url % urllib.quote(rev)
- else:
- return None
-
- return filter
- else:
- return lambda rev, repo: replace % urllib.quote(rev)
-
- assert False, '_replace has a bad type, but we should never get here'
-
-
-def _revlinkmacros(replace, templates):
- '''return macros for use with revision links, depending
- on whether revlinks are configured or not'''
-
- macros = templates.get_template("revmacros.html").module
-
- if not replace:
- id = macros.id
- short = macros.shorten
- else:
- id = macros.id_replace
- short = macros.shorten_replace
-
- return (id, short)
-
-
-def shortrevfilter(replace, templates):
- ''' Returns a function which shortens the revisison string
- to 12-chars (chosen as this is the Mercurial short-id length)
- and add link if replacement string is set.
-
- (The full id is still visible in HTML, for mouse-over events etc.)
-
- @param replace: see revlinkfilter()
- @param templates: a jinja2 environment
- '''
-
- url_f = _revlinkcfg(replace, templates)
-
- def filter(rev, repo):
- if not rev:
- return u''
-
- id_html, short_html = _revlinkmacros(replace, templates)
- rev = unicode(rev)
- url = url_f(rev, repo)
- rev = jinja2.escape(rev)
- shortrev = rev[:12] # TODO: customize this depending on vc type
-
- if shortrev == rev:
- if url:
- return id_html(rev=rev, url=url)
- else:
- return rev
- else:
- if url:
- return short_html(short=shortrev, rev=rev, url=url)
- else:
- return shortrev + '...'
-
- return filter
-
-
-def revlinkfilter(replace, templates):
- ''' Returns a function which adds an url link to a
- revision identifiers.
-
- Takes same params as shortrevfilter()
-
- @param replace: either a python format string with an %s,
- or a dict mapping repositories to format strings,
- or a callable taking (revision, repository) arguments
- and return an URL (or None, if no URL is available),
- or None, in which case revisions do not get decorated
- with links
-
- @param templates: a jinja2 environment
- '''
-
- url_f = _revlinkcfg(replace, templates)
-
- def filter(rev, repo):
- if not rev:
- return u''
-
- rev = unicode(rev)
- url = url_f(rev, repo)
- if url:
- id_html, _ = _revlinkmacros(replace, templates)
- return id_html(rev=rev, url=url)
- else:
- return jinja2.escape(rev)
-
- return filter
-
-
-def changelinkfilter(changelink):
- ''' Returns function that does regex search/replace in
- comments to add links to bug ids and similar.
-
- @param changelink:
- Either C{None}
- or: a tuple (2 or 3 elements)
- 1. a regex to match what we look for
- 2. an url with regex refs (\g<0>, \1, \2, etc) that becomes the 'href' attribute
- 3. (optional) an title string with regex ref regex
- or: a dict mapping projects to above tuples
- (no links will be added if the project isn't found)
- or: a callable taking (changehtml, project) args
- (where the changetext is HTML escaped in the
- form of a jinja2.Markup instance) and
- returning another jinja2.Markup instance with
- the same change text plus any HTML tags added to it.
- '''
-
- assert not changelink or isinstance(changelink, dict) or \
- isinstance(changelink, tuple) or callable(changelink)
-
- def replace_from_tuple(t):
- search, url_replace = t[:2]
- if len(t) == 3:
- title_replace = t[2]
- else:
- title_replace = ''
-
- search_re = re.compile(search)
-
- def replacement_unmatched(text):
- return jinja2.escape(text)
- def replacement_matched(mo):
- # expand things *after* application of the regular expressions
- url = jinja2.escape(mo.expand(url_replace))
- title = jinja2.escape(mo.expand(title_replace))
- body = jinja2.escape(mo.group())
- if title:
- return '<a href="%s" title="%s">%s</a>' % (url, title, body)
- else:
- return '<a href="%s">%s</a>' % (url, body)
-
- def filter(text, project):
- # now, we need to split the string into matched and unmatched portions,
- # quoting the unmatched portions directly and quoting the components of
- # the 'a' element for the matched portions. We can't use re.split here,
- # because the user-supplied patterns may have multiple groups.
- html = []
- last_idx = 0
- for mo in search_re.finditer(text):
- html.append(replacement_unmatched(text[last_idx:mo.start()]))
- html.append(replacement_matched(mo))
- last_idx = mo.end()
- html.append(replacement_unmatched(text[last_idx:]))
- return jinja2.Markup(''.join(html))
-
- return filter
-
- if not changelink:
- return lambda text, project: jinja2.escape(text)
-
- elif isinstance(changelink, dict):
- def dict_filter(text, project):
- # TODO: Optimize and cache return value from replace_from_tuple so
- # we only compile regex once per project, not per view
-
- t = changelink.get(project)
- if t:
- return replace_from_tuple(t)(text, project)
- else:
- return cgi.escape(text)
-
- return dict_filter
-
- elif isinstance(changelink, tuple):
- return replace_from_tuple(changelink)
-
- elif callable(changelink):
- def callable_filter(text, project):
- text = jinja2.escape(text)
- return changelink(text, project)
-
- return callable_filter
-
- assert False, 'changelink has unsupported type, but that is checked before'
-
-
-def dictlinkfilter(links):
- '''A filter that encloses the given value in a link tag
- given that the value exists in the dictionary'''
-
- assert not links or callable(links) or isinstance(links, dict)
-
- if not links:
- return jinja2.escape
-
- def filter(key):
- if callable(links):
- url = links(key)
- else:
- url = links.get(key)
-
- safe_key = jinja2.escape(key)
-
- if url:
- return jinja2.Markup(r'<a href="%s">%s</a>' % (url, safe_key))
- else:
- return safe_key
-
- return filter
-
-class AlmostStrictUndefined(jinja2.StrictUndefined):
- ''' An undefined that allows boolean testing but
- fails properly on every other use.
-
- Much better than the default Undefined, but not
- fully as strict as StrictUndefined '''
- def __nonzero__(self):
- return False
-
-_charsetRe = re.compile('charset=([^;]*)', re.I)
-def getRequestCharset(req):
- """Get the charset for an x-www-form-urlencoded request"""
- # per http://stackoverflow.com/questions/708915/detecting-the-character-encoding-of-an-http-post-request
- hdr = req.getHeader('Content-Type')
- if hdr:
- mo = _charsetRe.search(hdr)
- if mo:
- return mo.group(1).strip()
- return 'utf-8' # reasonable guess, works for ascii