OLD | NEW |
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 """ | 2 """ |
3 jinja2.utils | 3 jinja2.utils |
4 ~~~~~~~~~~~~ | 4 ~~~~~~~~~~~~ |
5 | 5 |
6 Utility functions. | 6 Utility functions. |
7 | 7 |
8 :copyright: (c) 2010 by the Jinja Team. | 8 :copyright: (c) 2010 by the Jinja Team. |
9 :license: BSD, see LICENSE for more details. | 9 :license: BSD, see LICENSE for more details. |
10 """ | 10 """ |
11 import re | 11 import re |
12 import errno | 12 import errno |
13 from collections import deque | 13 from collections import deque |
| 14 from threading import Lock |
14 from jinja2._compat import text_type, string_types, implements_iterator, \ | 15 from jinja2._compat import text_type, string_types, implements_iterator, \ |
15 allocate_lock, url_quote | 16 url_quote |
16 | 17 |
17 | 18 |
18 _word_split_re = re.compile(r'(\s+)') | 19 _word_split_re = re.compile(r'(\s+)') |
19 _punctuation_re = re.compile( | 20 _punctuation_re = re.compile( |
20 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % ( | 21 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % ( |
21 '|'.join(map(re.escape, ('(', '<', '<'))), | 22 '|'.join(map(re.escape, ('(', '<', '<'))), |
22 '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '>'))) | 23 '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '>'))) |
23 ) | 24 ) |
24 ) | 25 ) |
25 _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') | 26 _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
142 raise | 143 raise |
143 | 144 |
144 | 145 |
145 def open_if_exists(filename, mode='rb'): | 146 def open_if_exists(filename, mode='rb'): |
146 """Returns a file descriptor for the filename if that file exists, | 147 """Returns a file descriptor for the filename if that file exists, |
147 otherwise `None`. | 148 otherwise `None`. |
148 """ | 149 """ |
149 try: | 150 try: |
150 return open(filename, mode) | 151 return open(filename, mode) |
151 except IOError as e: | 152 except IOError as e: |
152 if e.errno not in (errno.ENOENT, errno.EISDIR): | 153 if e.errno not in (errno.ENOENT, errno.EISDIR, errno.EINVAL): |
153 raise | 154 raise |
154 | 155 |
155 | 156 |
156 def object_type_repr(obj): | 157 def object_type_repr(obj): |
157 """Returns the name of the object's type. For some recognized | 158 """Returns the name of the object's type. For some recognized |
158 singletons the name of the object is returned instead. (For | 159 singletons the name of the object is returned instead. (For |
159 example for `None` and `Ellipsis`). | 160 example for `None` and `Ellipsis`). |
160 """ | 161 """ |
161 if obj is None: | 162 if obj is None: |
162 return 'None' | 163 return 'None' |
(...skipping 12 matching lines...) Expand all Loading... |
175 builtin `pprint`. | 176 builtin `pprint`. |
176 """ | 177 """ |
177 try: | 178 try: |
178 from pretty import pretty | 179 from pretty import pretty |
179 return pretty(obj, verbose=verbose) | 180 return pretty(obj, verbose=verbose) |
180 except ImportError: | 181 except ImportError: |
181 from pprint import pformat | 182 from pprint import pformat |
182 return pformat(obj) | 183 return pformat(obj) |
183 | 184 |
184 | 185 |
185 def urlize(text, trim_url_limit=None, nofollow=False): | 186 def urlize(text, trim_url_limit=None, nofollow=False, target=None): |
186 """Converts any URLs in text into clickable links. Works on http://, | 187 """Converts any URLs in text into clickable links. Works on http://, |
187 https:// and www. links. Links can have trailing punctuation (periods, | 188 https:// and www. links. Links can have trailing punctuation (periods, |
188 commas, close-parens) and leading punctuation (opening parens) and | 189 commas, close-parens) and leading punctuation (opening parens) and |
189 it'll still do the right thing. | 190 it'll still do the right thing. |
190 | 191 |
191 If trim_url_limit is not None, the URLs in link text will be limited | 192 If trim_url_limit is not None, the URLs in link text will be limited |
192 to trim_url_limit characters. | 193 to trim_url_limit characters. |
193 | 194 |
194 If nofollow is True, the URLs in link text will get a rel="nofollow" | 195 If nofollow is True, the URLs in link text will get a rel="nofollow" |
195 attribute. | 196 attribute. |
| 197 |
| 198 If target is not None, a target attribute will be added to the link. |
196 """ | 199 """ |
197 trim_url = lambda x, limit=trim_url_limit: limit is not None \ | 200 trim_url = lambda x, limit=trim_url_limit: limit is not None \ |
198 and (x[:limit] + (len(x) >=limit and '...' | 201 and (x[:limit] + (len(x) >=limit and '...' |
199 or '')) or x | 202 or '')) or x |
200 words = _word_split_re.split(text_type(escape(text))) | 203 words = _word_split_re.split(text_type(escape(text))) |
201 nofollow_attr = nofollow and ' rel="nofollow"' or '' | 204 nofollow_attr = nofollow and ' rel="nofollow"' or '' |
| 205 if target is not None and isinstance(target, string_types): |
| 206 target_attr = ' target="%s"' % target |
| 207 else: |
| 208 target_attr = '' |
202 for i, word in enumerate(words): | 209 for i, word in enumerate(words): |
203 match = _punctuation_re.match(word) | 210 match = _punctuation_re.match(word) |
204 if match: | 211 if match: |
205 lead, middle, trail = match.groups() | 212 lead, middle, trail = match.groups() |
206 if middle.startswith('www.') or ( | 213 if middle.startswith('www.') or ( |
207 '@' not in middle and | 214 '@' not in middle and |
208 not middle.startswith('http://') and | 215 not middle.startswith('http://') and |
209 not middle.startswith('https://') and | 216 not middle.startswith('https://') and |
210 len(middle) > 0 and | 217 len(middle) > 0 and |
211 middle[0] in _letters + _digits and ( | 218 middle[0] in _letters + _digits and ( |
212 middle.endswith('.org') or | 219 middle.endswith('.org') or |
213 middle.endswith('.net') or | 220 middle.endswith('.net') or |
214 middle.endswith('.com') | 221 middle.endswith('.com') |
215 )): | 222 )): |
216 middle = '<a href="http://%s"%s>%s</a>' % (middle, | 223 middle = '<a href="http://%s"%s%s>%s</a>' % (middle, |
217 nofollow_attr, trim_url(middle)) | 224 nofollow_attr, target_attr, trim_url(middle)) |
218 if middle.startswith('http://') or \ | 225 if middle.startswith('http://') or \ |
219 middle.startswith('https://'): | 226 middle.startswith('https://'): |
220 middle = '<a href="%s"%s>%s</a>' % (middle, | 227 middle = '<a href="%s"%s%s>%s</a>' % (middle, |
221 nofollow_attr, trim_url(middle)) | 228 nofollow_attr, target_attr, trim_url(middle)) |
222 if '@' in middle and not middle.startswith('www.') and \ | 229 if '@' in middle and not middle.startswith('www.') and \ |
223 not ':' in middle and _simple_email_re.match(middle): | 230 not ':' in middle and _simple_email_re.match(middle): |
224 middle = '<a href="mailto:%s">%s</a>' % (middle, middle) | 231 middle = '<a href="mailto:%s">%s</a>' % (middle, middle) |
225 if lead + middle + trail != word: | 232 if lead + middle + trail != word: |
226 words[i] = lead + middle + trail | 233 words[i] = lead + middle + trail |
227 return u''.join(words) | 234 return u''.join(words) |
228 | 235 |
229 | 236 |
230 def generate_lorem_ipsum(n=5, html=True, min=20, max=100): | 237 def generate_lorem_ipsum(n=5, html=True, min=20, max=100): |
231 """Generate some lorem impsum for the template.""" | 238 """Generate some lorem ipsum for the template.""" |
232 from jinja2.constants import LOREM_IPSUM_WORDS | 239 from jinja2.constants import LOREM_IPSUM_WORDS |
233 from random import choice, randrange | 240 from random import choice, randrange |
234 words = LOREM_IPSUM_WORDS.split() | 241 words = LOREM_IPSUM_WORDS.split() |
235 result = [] | 242 result = [] |
236 | 243 |
237 for _ in range(n): | 244 for _ in range(n): |
238 next_capitalized = True | 245 next_capitalized = True |
239 last_comma = last_fullstop = 0 | 246 last_comma = last_fullstop = 0 |
240 word = None | 247 word = None |
241 last = None | 248 last = None |
(...skipping 27 matching lines...) Expand all Loading... |
269 p = p[:-1] + '.' | 276 p = p[:-1] + '.' |
270 elif not p.endswith('.'): | 277 elif not p.endswith('.'): |
271 p += '.' | 278 p += '.' |
272 result.append(p) | 279 result.append(p) |
273 | 280 |
274 if not html: | 281 if not html: |
275 return u'\n\n'.join(result) | 282 return u'\n\n'.join(result) |
276 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result)) | 283 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result)) |
277 | 284 |
278 | 285 |
279 def unicode_urlencode(obj, charset='utf-8'): | 286 def unicode_urlencode(obj, charset='utf-8', for_qs=False): |
280 """URL escapes a single bytestring or unicode string with the | 287 """URL escapes a single bytestring or unicode string with the |
281 given charset if applicable to URL safe quoting under all rules | 288 given charset if applicable to URL safe quoting under all rules |
282 that need to be considered under all supported Python versions. | 289 that need to be considered under all supported Python versions. |
283 | 290 |
284 If non strings are provided they are converted to their unicode | 291 If non strings are provided they are converted to their unicode |
285 representation first. | 292 representation first. |
286 """ | 293 """ |
287 if not isinstance(obj, string_types): | 294 if not isinstance(obj, string_types): |
288 obj = text_type(obj) | 295 obj = text_type(obj) |
289 if isinstance(obj, text_type): | 296 if isinstance(obj, text_type): |
290 obj = obj.encode(charset) | 297 obj = obj.encode(charset) |
291 return text_type(url_quote(obj)) | 298 safe = for_qs and b'' or b'/' |
| 299 rv = text_type(url_quote(obj, safe)) |
| 300 if for_qs: |
| 301 rv = rv.replace('%20', '+') |
| 302 return rv |
292 | 303 |
293 | 304 |
294 class LRUCache(object): | 305 class LRUCache(object): |
295 """A simple LRU Cache implementation.""" | 306 """A simple LRU Cache implementation.""" |
296 | 307 |
297 # this is fast for small capacities (something below 1000) but doesn't | 308 # this is fast for small capacities (something below 1000) but doesn't |
298 # scale. But as long as it's only used as storage for templates this | 309 # scale. But as long as it's only used as storage for templates this |
299 # won't do any harm. | 310 # won't do any harm. |
300 | 311 |
301 def __init__(self, capacity): | 312 def __init__(self, capacity): |
302 self.capacity = capacity | 313 self.capacity = capacity |
303 self._mapping = {} | 314 self._mapping = {} |
304 self._queue = deque() | 315 self._queue = deque() |
305 self._postinit() | 316 self._postinit() |
306 | 317 |
307 def _postinit(self): | 318 def _postinit(self): |
308 # alias all queue methods for faster lookup | 319 # alias all queue methods for faster lookup |
309 self._popleft = self._queue.popleft | 320 self._popleft = self._queue.popleft |
310 self._pop = self._queue.pop | 321 self._pop = self._queue.pop |
311 self._remove = self._queue.remove | 322 self._remove = self._queue.remove |
312 self._wlock = allocate_lock() | 323 self._wlock = Lock() |
313 self._append = self._queue.append | 324 self._append = self._queue.append |
314 | 325 |
315 def __getstate__(self): | 326 def __getstate__(self): |
316 return { | 327 return { |
317 'capacity': self.capacity, | 328 'capacity': self.capacity, |
318 '_mapping': self._mapping, | 329 '_mapping': self._mapping, |
319 '_queue': self._queue | 330 '_queue': self._queue |
320 } | 331 } |
321 | 332 |
322 def __setstate__(self, d): | 333 def __setstate__(self, d): |
(...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
511 | 522 |
512 def __call__(self): | 523 def __call__(self): |
513 if not self.used: | 524 if not self.used: |
514 self.used = True | 525 self.used = True |
515 return u'' | 526 return u'' |
516 return self.sep | 527 return self.sep |
517 | 528 |
518 | 529 |
519 # Imported here because that's where it was in the past | 530 # Imported here because that's where it was in the past |
520 from markupsafe import Markup, escape, soft_unicode | 531 from markupsafe import Markup, escape, soft_unicode |
OLD | NEW |