PageRenderTime 59ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/External.LCA_RESTRICTED/Languages/IronPython/27/Doc/jinja2/utils.py

http://github.com/IronLanguages/main
Python | 780 lines | 676 code | 32 blank | 72 comment | 20 complexity | 954155f81d91e6a892349490768f5fbd MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception
  1. # -*- coding: utf-8 -*-
  2. """
  3. jinja2.utils
  4. ~~~~~~~~~~~~
  5. Utility functions.
  6. :copyright: (c) 2009 by the Jinja Team.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. import re
  10. import sys
  11. import errno
  12. try:
  13. from thread import allocate_lock
  14. except ImportError:
  15. from dummy_thread import allocate_lock
  16. from collections import deque
  17. from itertools import imap
  18. _word_split_re = re.compile(r'(\s+)')
  19. _punctuation_re = re.compile(
  20. '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
  21. '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
  22. '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
  23. )
  24. )
  25. _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
  26. _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
  27. _entity_re = re.compile(r'&([^;]+);')
  28. _letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  29. _digits = '0123456789'
  30. # special singleton representing missing values for the runtime
  31. missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
  32. # internal code
  33. internal_code = set()
  34. # concatenate a list of strings and convert them to unicode.
  35. # unfortunately there is a bug in python 2.4 and lower that causes
  36. # unicode.join trash the traceback.
  37. _concat = u''.join
  38. try:
  39. def _test_gen_bug():
  40. raise TypeError(_test_gen_bug)
  41. yield None
  42. _concat(_test_gen_bug())
  43. except TypeError, _error:
  44. if not _error.args or _error.args[0] is not _test_gen_bug:
  45. def concat(gen):
  46. try:
  47. return _concat(list(gen))
  48. except:
  49. # this hack is needed so that the current frame
  50. # does not show up in the traceback.
  51. exc_type, exc_value, tb = sys.exc_info()
  52. raise exc_type, exc_value, tb.tb_next
  53. else:
  54. concat = _concat
  55. del _test_gen_bug, _error
  56. # for python 2.x we create outselves a next() function that does the
  57. # basics without exception catching.
  58. try:
  59. next = next
  60. except NameError:
  61. def next(x):
  62. return x.next()
  63. # ironpython without stdlib doesn't have keyword
  64. try:
  65. from keyword import iskeyword as is_python_keyword
  66. except ImportError:
  67. _py_identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9]*$')
  68. def is_python_keyword(name):
  69. if _py_identifier_re.search(name) is None:
  70. return False
  71. try:
  72. exec name + " = 42"
  73. except SyntaxError:
  74. return False
  75. return True
  76. # common types. These do exist in the special types module too which however
  77. # does not exist in IronPython out of the box.
  78. class _C(object):
  79. def method(self): pass
  80. def _func():
  81. yield None
  82. FunctionType = type(_func)
  83. GeneratorType = type(_func())
  84. MethodType = type(_C.method)
  85. CodeType = type(_C.method.func_code)
  86. try:
  87. raise TypeError()
  88. except TypeError:
  89. _tb = sys.exc_info()[2]
  90. TracebackType = type(_tb)
  91. FrameType = type(_tb.tb_frame)
  92. del _C, _tb, _func
  93. def contextfunction(f):
  94. """This decorator can be used to mark a function or method context callable.
  95. A context callable is passed the active :class:`Context` as first argument when
  96. called from the template. This is useful if a function wants to get access
  97. to the context or functions provided on the context object. For example
  98. a function that returns a sorted list of template variables the current
  99. template exports could look like this::
  100. @contextfunction
  101. def get_exported_names(context):
  102. return sorted(context.exported_vars)
  103. """
  104. f.contextfunction = True
  105. return f
  106. def environmentfunction(f):
  107. """This decorator can be used to mark a function or method as environment
  108. callable. This decorator works exactly like the :func:`contextfunction`
  109. decorator just that the first argument is the active :class:`Environment`
  110. and not context.
  111. """
  112. f.environmentfunction = True
  113. return f
  114. def internalcode(f):
  115. """Marks the function as internally used"""
  116. internal_code.add(f.func_code)
  117. return f
  118. def is_undefined(obj):
  119. """Check if the object passed is undefined. This does nothing more than
  120. performing an instance check against :class:`Undefined` but looks nicer.
  121. This can be used for custom filters or tests that want to react to
  122. undefined variables. For example a custom default filter can look like
  123. this::
  124. def default(var, default=''):
  125. if is_undefined(var):
  126. return default
  127. return var
  128. """
  129. from jinja2.runtime import Undefined
  130. return isinstance(obj, Undefined)
  131. def consume(iterable):
  132. """Consumes an iterable without doing anything with it."""
  133. for event in iterable:
  134. pass
  135. def clear_caches():
  136. """Jinja2 keeps internal caches for environments and lexers. These are
  137. used so that Jinja2 doesn't have to recreate environments and lexers all
  138. the time. Normally you don't have to care about that but if you are
  139. messuring memory consumption you may want to clean the caches.
  140. """
  141. from jinja2.environment import _spontaneous_environments
  142. from jinja2.lexer import _lexer_cache
  143. _spontaneous_environments.clear()
  144. _lexer_cache.clear()
  145. def import_string(import_name, silent=False):
  146. """Imports an object based on a string. This use useful if you want to
  147. use import paths as endpoints or something similar. An import path can
  148. be specified either in dotted notation (``xml.sax.saxutils.escape``)
  149. or with a colon as object delimiter (``xml.sax.saxutils:escape``).
  150. If the `silent` is True the return value will be `None` if the import
  151. fails.
  152. :return: imported object
  153. """
  154. try:
  155. if ':' in import_name:
  156. module, obj = import_name.split(':', 1)
  157. elif '.' in import_name:
  158. items = import_name.split('.')
  159. module = '.'.join(items[:-1])
  160. obj = items[-1]
  161. else:
  162. return __import__(import_name)
  163. return getattr(__import__(module, None, None, [obj]), obj)
  164. except (ImportError, AttributeError):
  165. if not silent:
  166. raise
  167. def open_if_exists(filename, mode='r'):
  168. """Returns a file descriptor for the filename if that file exists,
  169. otherwise `None`.
  170. """
  171. try:
  172. return file(filename, mode)
  173. except IOError, e:
  174. if e.errno not in (errno.ENOENT, errno.EISDIR):
  175. raise
  176. def pformat(obj, verbose=False):
  177. """Prettyprint an object. Either use the `pretty` library or the
  178. builtin `pprint`.
  179. """
  180. try:
  181. from pretty import pretty
  182. return pretty(obj, verbose=verbose)
  183. except ImportError:
  184. from pprint import pformat
  185. return pformat(obj)
  186. def urlize(text, trim_url_limit=None, nofollow=False):
  187. """Converts any URLs in text into clickable links. Works on http://,
  188. https:// and www. links. Links can have trailing punctuation (periods,
  189. commas, close-parens) and leading punctuation (opening parens) and
  190. it'll still do the right thing.
  191. If trim_url_limit is not None, the URLs in link text will be limited
  192. to trim_url_limit characters.
  193. If nofollow is True, the URLs in link text will get a rel="nofollow"
  194. attribute.
  195. """
  196. trim_url = lambda x, limit=trim_url_limit: limit is not None \
  197. and (x[:limit] + (len(x) >=limit and '...'
  198. or '')) or x
  199. words = _word_split_re.split(unicode(escape(text)))
  200. nofollow_attr = nofollow and ' rel="nofollow"' or ''
  201. for i, word in enumerate(words):
  202. match = _punctuation_re.match(word)
  203. if match:
  204. lead, middle, trail = match.groups()
  205. if middle.startswith('www.') or (
  206. '@' not in middle and
  207. not middle.startswith('http://') and
  208. len(middle) > 0 and
  209. middle[0] in _letters + _digits and (
  210. middle.endswith('.org') or
  211. middle.endswith('.net') or
  212. middle.endswith('.com')
  213. )):
  214. middle = '<a href="http://%s"%s>%s</a>' % (middle,
  215. nofollow_attr, trim_url(middle))
  216. if middle.startswith('http://') or \
  217. middle.startswith('https://'):
  218. middle = '<a href="%s"%s>%s</a>' % (middle,
  219. nofollow_attr, trim_url(middle))
  220. if '@' in middle and not middle.startswith('www.') and \
  221. not ':' in middle and _simple_email_re.match(middle):
  222. middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
  223. if lead + middle + trail != word:
  224. words[i] = lead + middle + trail
  225. return u''.join(words)
  226. def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
  227. """Generate some lorem impsum for the template."""
  228. from jinja2.constants import LOREM_IPSUM_WORDS
  229. from random import choice, random, randrange
  230. words = LOREM_IPSUM_WORDS.split()
  231. result = []
  232. for _ in xrange(n):
  233. next_capitalized = True
  234. last_comma = last_fullstop = 0
  235. word = None
  236. last = None
  237. p = []
  238. # each paragraph contains out of 20 to 100 words.
  239. for idx, _ in enumerate(xrange(randrange(min, max))):
  240. while True:
  241. word = choice(words)
  242. if word != last:
  243. last = word
  244. break
  245. if next_capitalized:
  246. word = word.capitalize()
  247. next_capitalized = False
  248. # add commas
  249. if idx - randrange(3, 8) > last_comma:
  250. last_comma = idx
  251. last_fullstop += 2
  252. word += ','
  253. # add end of sentences
  254. if idx - randrange(10, 20) > last_fullstop:
  255. last_comma = last_fullstop = idx
  256. word += '.'
  257. next_capitalized = True
  258. p.append(word)
  259. # ensure that the paragraph ends with a dot.
  260. p = u' '.join(p)
  261. if p.endswith(','):
  262. p = p[:-1] + '.'
  263. elif not p.endswith('.'):
  264. p += '.'
  265. result.append(p)
  266. if not html:
  267. return u'\n\n'.join(result)
  268. return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
  269. class Markup(unicode):
  270. r"""Marks a string as being safe for inclusion in HTML/XML output without
  271. needing to be escaped. This implements the `__html__` interface a couple
  272. of frameworks and web applications use. :class:`Markup` is a direct
  273. subclass of `unicode` and provides all the methods of `unicode` just that
  274. it escapes arguments passed and always returns `Markup`.
  275. The `escape` function returns markup objects so that double escaping can't
  276. happen. If you want to use autoescaping in Jinja just enable the
  277. autoescaping feature in the environment.
  278. The constructor of the :class:`Markup` class can be used for three
  279. different things: When passed an unicode object it's assumed to be safe,
  280. when passed an object with an HTML representation (has an `__html__`
  281. method) that representation is used, otherwise the object passed is
  282. converted into a unicode string and then assumed to be safe:
  283. >>> Markup("Hello <em>World</em>!")
  284. Markup(u'Hello <em>World</em>!')
  285. >>> class Foo(object):
  286. ... def __html__(self):
  287. ... return '<a href="#">foo</a>'
  288. ...
  289. >>> Markup(Foo())
  290. Markup(u'<a href="#">foo</a>')
  291. If you want object passed being always treated as unsafe you can use the
  292. :meth:`escape` classmethod to create a :class:`Markup` object:
  293. >>> Markup.escape("Hello <em>World</em>!")
  294. Markup(u'Hello &lt;em&gt;World&lt;/em&gt;!')
  295. Operations on a markup string are markup aware which means that all
  296. arguments are passed through the :func:`escape` function:
  297. >>> em = Markup("<em>%s</em>")
  298. >>> em % "foo & bar"
  299. Markup(u'<em>foo &amp; bar</em>')
  300. >>> strong = Markup("<strong>%(text)s</strong>")
  301. >>> strong % {'text': '<blink>hacker here</blink>'}
  302. Markup(u'<strong>&lt;blink&gt;hacker here&lt;/blink&gt;</strong>')
  303. >>> Markup("<em>Hello</em> ") + "<foo>"
  304. Markup(u'<em>Hello</em> &lt;foo&gt;')
  305. """
  306. __slots__ = ()
  307. def __new__(cls, base=u'', encoding=None, errors='strict'):
  308. if hasattr(base, '__html__'):
  309. base = base.__html__()
  310. if encoding is None:
  311. return unicode.__new__(cls, base)
  312. return unicode.__new__(cls, base, encoding, errors)
  313. def __html__(self):
  314. return self
  315. def __add__(self, other):
  316. if hasattr(other, '__html__') or isinstance(other, basestring):
  317. return self.__class__(unicode(self) + unicode(escape(other)))
  318. return NotImplemented
  319. def __radd__(self, other):
  320. if hasattr(other, '__html__') or isinstance(other, basestring):
  321. return self.__class__(unicode(escape(other)) + unicode(self))
  322. return NotImplemented
  323. def __mul__(self, num):
  324. if isinstance(num, (int, long)):
  325. return self.__class__(unicode.__mul__(self, num))
  326. return NotImplemented
  327. __rmul__ = __mul__
  328. def __mod__(self, arg):
  329. if isinstance(arg, tuple):
  330. arg = tuple(imap(_MarkupEscapeHelper, arg))
  331. else:
  332. arg = _MarkupEscapeHelper(arg)
  333. return self.__class__(unicode.__mod__(self, arg))
  334. def __repr__(self):
  335. return '%s(%s)' % (
  336. self.__class__.__name__,
  337. unicode.__repr__(self)
  338. )
  339. def join(self, seq):
  340. return self.__class__(unicode.join(self, imap(escape, seq)))
  341. join.__doc__ = unicode.join.__doc__
  342. def split(self, *args, **kwargs):
  343. return map(self.__class__, unicode.split(self, *args, **kwargs))
  344. split.__doc__ = unicode.split.__doc__
  345. def rsplit(self, *args, **kwargs):
  346. return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
  347. rsplit.__doc__ = unicode.rsplit.__doc__
  348. def splitlines(self, *args, **kwargs):
  349. return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
  350. splitlines.__doc__ = unicode.splitlines.__doc__
  351. def unescape(self):
  352. r"""Unescape markup again into an unicode string. This also resolves
  353. known HTML4 and XHTML entities:
  354. >>> Markup("Main &raquo; <em>About</em>").unescape()
  355. u'Main \xbb <em>About</em>'
  356. """
  357. from jinja2.constants import HTML_ENTITIES
  358. def handle_match(m):
  359. name = m.group(1)
  360. if name in HTML_ENTITIES:
  361. return unichr(HTML_ENTITIES[name])
  362. try:
  363. if name[:2] in ('#x', '#X'):
  364. return unichr(int(name[2:], 16))
  365. elif name.startswith('#'):
  366. return unichr(int(name[1:]))
  367. except ValueError:
  368. pass
  369. return u''
  370. return _entity_re.sub(handle_match, unicode(self))
  371. def striptags(self):
  372. r"""Unescape markup into an unicode string and strip all tags. This
  373. also resolves known HTML4 and XHTML entities. Whitespace is
  374. normalized to one:
  375. >>> Markup("Main &raquo; <em>About</em>").striptags()
  376. u'Main \xbb About'
  377. """
  378. stripped = u' '.join(_striptags_re.sub('', self).split())
  379. return Markup(stripped).unescape()
  380. @classmethod
  381. def escape(cls, s):
  382. """Escape the string. Works like :func:`escape` with the difference
  383. that for subclasses of :class:`Markup` this function would return the
  384. correct subclass.
  385. """
  386. rv = escape(s)
  387. if rv.__class__ is not cls:
  388. return cls(rv)
  389. return rv
  390. def make_wrapper(name):
  391. orig = getattr(unicode, name)
  392. def func(self, *args, **kwargs):
  393. args = _escape_argspec(list(args), enumerate(args))
  394. _escape_argspec(kwargs, kwargs.iteritems())
  395. return self.__class__(orig(self, *args, **kwargs))
  396. func.__name__ = orig.__name__
  397. func.__doc__ = orig.__doc__
  398. return func
  399. for method in '__getitem__', 'capitalize', \
  400. 'title', 'lower', 'upper', 'replace', 'ljust', \
  401. 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
  402. 'translate', 'expandtabs', 'swapcase', 'zfill':
  403. locals()[method] = make_wrapper(method)
  404. # new in python 2.5
  405. if hasattr(unicode, 'partition'):
  406. partition = make_wrapper('partition'),
  407. rpartition = make_wrapper('rpartition')
  408. # new in python 2.6
  409. if hasattr(unicode, 'format'):
  410. format = make_wrapper('format')
  411. # not in python 3
  412. if hasattr(unicode, '__getslice__'):
  413. __getslice__ = make_wrapper('__getslice__')
  414. del method, make_wrapper
  415. def _escape_argspec(obj, iterable):
  416. """Helper for various string-wrapped functions."""
  417. for key, value in iterable:
  418. if hasattr(value, '__html__') or isinstance(value, basestring):
  419. obj[key] = escape(value)
  420. return obj
  421. class _MarkupEscapeHelper(object):
  422. """Helper for Markup.__mod__"""
  423. def __init__(self, obj):
  424. self.obj = obj
  425. __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
  426. __unicode__ = lambda s: unicode(escape(s.obj))
  427. __str__ = lambda s: str(escape(s.obj))
  428. __repr__ = lambda s: str(escape(repr(s.obj)))
  429. __int__ = lambda s: int(s.obj)
  430. __float__ = lambda s: float(s.obj)
  431. class LRUCache(object):
  432. """A simple LRU Cache implementation."""
  433. # this is fast for small capacities (something below 1000) but doesn't
  434. # scale. But as long as it's only used as storage for templates this
  435. # won't do any harm.
  436. def __init__(self, capacity):
  437. self.capacity = capacity
  438. self._mapping = {}
  439. self._queue = deque()
  440. self._postinit()
  441. def _postinit(self):
  442. # alias all queue methods for faster lookup
  443. self._popleft = self._queue.popleft
  444. self._pop = self._queue.pop
  445. if hasattr(self._queue, 'remove'):
  446. self._remove = self._queue.remove
  447. self._wlock = allocate_lock()
  448. self._append = self._queue.append
  449. def _remove(self, obj):
  450. """Python 2.4 compatibility."""
  451. for idx, item in enumerate(self._queue):
  452. if item == obj:
  453. del self._queue[idx]
  454. break
  455. def __getstate__(self):
  456. return {
  457. 'capacity': self.capacity,
  458. '_mapping': self._mapping,
  459. '_queue': self._queue
  460. }
  461. def __setstate__(self, d):
  462. self.__dict__.update(d)
  463. self._postinit()
  464. def __getnewargs__(self):
  465. return (self.capacity,)
  466. def copy(self):
  467. """Return an shallow copy of the instance."""
  468. rv = self.__class__(self.capacity)
  469. rv._mapping.update(self._mapping)
  470. rv._queue = deque(self._queue)
  471. return rv
  472. def get(self, key, default=None):
  473. """Return an item from the cache dict or `default`"""
  474. try:
  475. return self[key]
  476. except KeyError:
  477. return default
  478. def setdefault(self, key, default=None):
  479. """Set `default` if the key is not in the cache otherwise
  480. leave unchanged. Return the value of this key.
  481. """
  482. try:
  483. return self[key]
  484. except KeyError:
  485. self[key] = default
  486. return default
  487. def clear(self):
  488. """Clear the cache."""
  489. self._wlock.acquire()
  490. try:
  491. self._mapping.clear()
  492. self._queue.clear()
  493. finally:
  494. self._wlock.release()
  495. def __contains__(self, key):
  496. """Check if a key exists in this cache."""
  497. return key in self._mapping
  498. def __len__(self):
  499. """Return the current size of the cache."""
  500. return len(self._mapping)
  501. def __repr__(self):
  502. return '<%s %r>' % (
  503. self.__class__.__name__,
  504. self._mapping
  505. )
  506. def __getitem__(self, key):
  507. """Get an item from the cache. Moves the item up so that it has the
  508. highest priority then.
  509. Raise an `KeyError` if it does not exist.
  510. """
  511. rv = self._mapping[key]
  512. if self._queue[-1] != key:
  513. try:
  514. self._remove(key)
  515. except ValueError:
  516. # if something removed the key from the container
  517. # when we read, ignore the ValueError that we would
  518. # get otherwise.
  519. pass
  520. self._append(key)
  521. return rv
  522. def __setitem__(self, key, value):
  523. """Sets the value for an item. Moves the item up so that it
  524. has the highest priority then.
  525. """
  526. self._wlock.acquire()
  527. try:
  528. if key in self._mapping:
  529. self._remove(key)
  530. elif len(self._mapping) == self.capacity:
  531. del self._mapping[self._popleft()]
  532. self._append(key)
  533. self._mapping[key] = value
  534. finally:
  535. self._wlock.release()
  536. def __delitem__(self, key):
  537. """Remove an item from the cache dict.
  538. Raise an `KeyError` if it does not exist.
  539. """
  540. self._wlock.acquire()
  541. try:
  542. del self._mapping[key]
  543. try:
  544. self._remove(key)
  545. except ValueError:
  546. # __getitem__ is not locked, it might happen
  547. pass
  548. finally:
  549. self._wlock.release()
  550. def items(self):
  551. """Return a list of items."""
  552. result = [(key, self._mapping[key]) for key in list(self._queue)]
  553. result.reverse()
  554. return result
  555. def iteritems(self):
  556. """Iterate over all items."""
  557. return iter(self.items())
  558. def values(self):
  559. """Return a list of all values."""
  560. return [x[1] for x in self.items()]
  561. def itervalue(self):
  562. """Iterate over all values."""
  563. return iter(self.values())
  564. def keys(self):
  565. """Return a list of all keys ordered by most recent usage."""
  566. return list(self)
  567. def iterkeys(self):
  568. """Iterate over all keys in the cache dict, ordered by
  569. the most recent usage.
  570. """
  571. return reversed(tuple(self._queue))
  572. __iter__ = iterkeys
  573. def __reversed__(self):
  574. """Iterate over the values in the cache dict, oldest items
  575. coming first.
  576. """
  577. return iter(tuple(self._queue))
  578. __copy__ = copy
  579. # register the LRU cache as mutable mapping if possible
  580. try:
  581. from collections import MutableMapping
  582. MutableMapping.register(LRUCache)
  583. except ImportError:
  584. pass
  585. class Cycler(object):
  586. """A cycle helper for templates."""
  587. def __init__(self, *items):
  588. if not items:
  589. raise RuntimeError('at least one item has to be provided')
  590. self.items = items
  591. self.reset()
  592. def reset(self):
  593. """Resets the cycle."""
  594. self.pos = 0
  595. @property
  596. def current(self):
  597. """Returns the current item."""
  598. return self.items[self.pos]
  599. def next(self):
  600. """Goes one item ahead and returns it."""
  601. rv = self.current
  602. self.pos = (self.pos + 1) % len(self.items)
  603. return rv
  604. class Joiner(object):
  605. """A joining helper for templates."""
  606. def __init__(self, sep=u', '):
  607. self.sep = sep
  608. self.used = False
  609. def __call__(self):
  610. if not self.used:
  611. self.used = True
  612. return u''
  613. return self.sep
  614. # we have to import it down here as the speedups module imports the
  615. # markup type which is define above.
  616. try:
  617. from jinja2._speedups import escape, soft_unicode
  618. except ImportError:
  619. def escape(s):
  620. """Convert the characters &, <, >, ' and " in string s to HTML-safe
  621. sequences. Use this if you need to display text that might contain
  622. such characters in HTML. Marks return value as markup string.
  623. """
  624. if hasattr(s, '__html__'):
  625. return s.__html__()
  626. return Markup(unicode(s)
  627. .replace('&', '&amp;')
  628. .replace('>', '&gt;')
  629. .replace('<', '&lt;')
  630. .replace("'", '&#39;')
  631. .replace('"', '&#34;')
  632. )
  633. def soft_unicode(s):
  634. """Make a string unicode if it isn't already. That way a markup
  635. string is not converted back to unicode.
  636. """
  637. if not isinstance(s, unicode):
  638. s = unicode(s)
  639. return s
  640. # partials
  641. try:
  642. from functools import partial
  643. except ImportError:
  644. class partial(object):
  645. def __init__(self, _func, *args, **kwargs):
  646. self._func = _func
  647. self._args = args
  648. self._kwargs = kwargs
  649. def __call__(self, *args, **kwargs):
  650. kwargs.update(self._kwargs)
  651. return self._func(*(self._args + args), **kwargs)